mirror of
https://github.com/openai/codex.git
synced 2026-04-26 15:45:02 +00:00
feat(linux-sandbox): vendor bubblewrap and wire it with FFI (#10413)
## Summary Vendor Bubblewrap into the repo and add minimal build plumbing in `codex-linux-sandbox` to compile/link it. ## Why We want to move Linux sandboxing toward Bubblewrap, but in a safe two-step rollout: 1) vendoring/build setup (this PR), 2) runtime integration (follow-up PR). ## Included - Add `codex-rs/vendor/bubblewrap` sources. - Add build-time FFI path in `codex-rs/linux-sandbox`. - Update `build.rs` rerun tracking for vendored files. - Small vendored compile warning fix (`sockaddr_nl` full init). follow up in https://github.com/openai/codex/pull/9938
This commit is contained in:
190
codex-rs/vendor/bubblewrap/tests/libtest-core.sh
vendored
Normal file
190
codex-rs/vendor/bubblewrap/tests/libtest-core.sh
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
# Core source library for shell script tests; the
|
||||
# canonical version lives in:
|
||||
#
|
||||
# https://github.com/ostreedev/ostree
|
||||
#
|
||||
# Known copies are in the following repos:
|
||||
#
|
||||
# - https://github.com/containers/bubblewrap
|
||||
# - https://github.com/coreos/rpm-ostree
|
||||
#
|
||||
# Copyright (C) 2017 Colin Walters <walters@verbum.org>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
|
||||
fatal() {
|
||||
echo $@ 1>&2; exit 1
|
||||
}
|
||||
# fatal() is shorter to type, but retain this alias
|
||||
assert_not_reached () {
|
||||
fatal "$@"
|
||||
}
|
||||
|
||||
# Some tests look for specific English strings. Use a UTF-8 version
|
||||
# of the C (POSIX) locale if we have one, or fall back to en_US.UTF-8
|
||||
# (https://sourceware.org/glibc/wiki/Proposals/C.UTF-8)
|
||||
#
|
||||
# If we can't find the locale command assume we have support for C.UTF-8
|
||||
# (e.g. musl based systems)
|
||||
if type -p locale >/dev/null; then
|
||||
export LC_ALL=$(locale -a | grep -iEe '^(C|en_US)\.(UTF-8|utf8)$' | head -n1 || true)
|
||||
if [ -z "${LC_ALL}" ]; then fatal "Can't find suitable UTF-8 locale"; fi
|
||||
else
|
||||
export LC_ALL=C.UTF-8
|
||||
fi
|
||||
# A GNU extension, used whenever LC_ALL is not C
|
||||
unset LANGUAGE
|
||||
|
||||
# This should really be the default IMO
|
||||
export G_DEBUG=fatal-warnings
|
||||
|
||||
assert_streq () {
|
||||
test "$1" = "$2" || fatal "$1 != $2"
|
||||
}
|
||||
|
||||
assert_str_match () {
|
||||
if ! echo "$1" | grep -E -q "$2"; then
|
||||
fatal "$1 does not match regexp $2"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_streq () {
|
||||
(! test "$1" = "$2") || fatal "$1 == $2"
|
||||
}
|
||||
|
||||
assert_has_file () {
|
||||
test -f "$1" || fatal "Couldn't find '$1'"
|
||||
}
|
||||
|
||||
assert_has_dir () {
|
||||
test -d "$1" || fatal "Couldn't find '$1'"
|
||||
}
|
||||
|
||||
# Dump ls -al + file contents to stderr, then fatal()
|
||||
_fatal_print_file() {
|
||||
file="$1"
|
||||
shift
|
||||
ls -al "$file" >&2
|
||||
sed -e 's/^/# /' < "$file" >&2
|
||||
fatal "$@"
|
||||
}
|
||||
|
||||
_fatal_print_files() {
|
||||
file1="$1"
|
||||
shift
|
||||
file2="$1"
|
||||
shift
|
||||
ls -al "$file1" >&2
|
||||
sed -e 's/^/# /' < "$file1" >&2
|
||||
ls -al "$file2" >&2
|
||||
sed -e 's/^/# /' < "$file2" >&2
|
||||
fatal "$@"
|
||||
}
|
||||
|
||||
assert_not_has_file () {
|
||||
if test -f "$1"; then
|
||||
_fatal_print_file "$1" "File '$1' exists"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_file_has_content () {
|
||||
fpath=$1
|
||||
shift
|
||||
for re in "$@"; do
|
||||
if grep -q -e "$re" "$fpath"; then
|
||||
_fatal_print_file "$fpath" "File '$fpath' matches regexp '$re'"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
assert_not_has_dir () {
|
||||
if test -d "$1"; then
|
||||
fatal "Directory '$1' exists"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_has_content () {
|
||||
fpath=$1
|
||||
shift
|
||||
for re in "$@"; do
|
||||
if ! grep -q -e "$re" "$fpath"; then
|
||||
_fatal_print_file "$fpath" "File '$fpath' doesn't match regexp '$re'"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
assert_file_has_content_once () {
|
||||
fpath=$1
|
||||
shift
|
||||
for re in "$@"; do
|
||||
if ! test $(grep -e "$re" "$fpath" | wc -l) = "1"; then
|
||||
_fatal_print_file "$fpath" "File '$fpath' doesn't match regexp '$re' exactly once"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
assert_file_has_content_literal () {
|
||||
fpath=$1; shift
|
||||
for s in "$@"; do
|
||||
if ! grep -q -F -e "$s" "$fpath"; then
|
||||
_fatal_print_file "$fpath" "File '$fpath' doesn't match fixed string list '$s'"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
assert_file_has_mode () {
|
||||
mode=$(stat -c '%a' $1)
|
||||
if [ "$mode" != "$2" ]; then
|
||||
fatal "File '$1' has wrong mode: expected $2, but got $mode"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_symlink_has_content () {
|
||||
if ! test -L "$1"; then
|
||||
fatal "File '$1' is not a symbolic link"
|
||||
fi
|
||||
if ! readlink "$1" | grep -q -e "$2"; then
|
||||
_fatal_print_file "$1" "Symbolic link '$1' doesn't match regexp '$2'"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_empty() {
|
||||
if test -s "$1"; then
|
||||
_fatal_print_file "$1" "File '$1' is not empty"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_files_equal() {
|
||||
if ! cmp "$1" "$2"; then
|
||||
_fatal_print_files "$1" "$2" "File '$1' and '$2' is not equal"
|
||||
fi
|
||||
}
|
||||
|
||||
# Use to skip all of these tests
|
||||
skip() {
|
||||
echo "1..0 # SKIP" "$@"
|
||||
exit 0
|
||||
}
|
||||
|
||||
report_err () {
|
||||
local exit_status="$?"
|
||||
{ { local BASH_XTRACEFD=3; } 2> /dev/null
|
||||
echo "Unexpected nonzero exit status $exit_status while running: $BASH_COMMAND" >&2
|
||||
} 3> /dev/null
|
||||
}
|
||||
trap report_err ERR
|
||||
115
codex-rs/vendor/bubblewrap/tests/libtest.sh
vendored
Normal file
115
codex-rs/vendor/bubblewrap/tests/libtest.sh
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Source library for shell script tests.
|
||||
# Add non-bubblewrap-specific code to libtest-core.sh instead.
|
||||
#
|
||||
# Copyright (C) 2017 Colin Walters <walters@verbum.org>
|
||||
# SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
# Boston, MA 02111-1307, USA.
|
||||
|
||||
set -e
|
||||
|
||||
if [ -n "${G_TEST_SRCDIR:-}" ]; then
|
||||
test_srcdir="${G_TEST_SRCDIR}/tests"
|
||||
else
|
||||
test_srcdir=$(dirname "$0")
|
||||
fi
|
||||
|
||||
if [ -n "${G_TEST_BUILDDIR:-}" ]; then
|
||||
test_builddir="${G_TEST_BUILDDIR}/tests"
|
||||
else
|
||||
test_builddir=$(dirname "$0")
|
||||
fi
|
||||
|
||||
. "${test_srcdir}/libtest-core.sh"
|
||||
|
||||
# Make sure /sbin/getpcaps etc. are in our PATH even if non-root
|
||||
PATH="$PATH:/usr/sbin:/sbin"
|
||||
|
||||
tempdir=$(mktemp -d /var/tmp/tap-test.XXXXXX)
|
||||
touch "${tempdir}/.testtmp"
|
||||
cleanup() {
|
||||
if test -n "${TEST_SKIP_CLEANUP:-}"; then
|
||||
echo "Skipping cleanup of ${tempdir}"
|
||||
elif test -f "${tempdir}/.testtmp"; then
|
||||
rm -rf "${tempdir}"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
cd "${tempdir}"
|
||||
|
||||
: "${BWRAP:=bwrap}"
|
||||
if test -u "$(type -p ${BWRAP})"; then
|
||||
bwrap_is_suid=true
|
||||
fi
|
||||
|
||||
FUSE_DIR=
|
||||
for mp in $(grep " fuse[. ]" /proc/self/mounts | grep "user_id=$(id -u)" | awk '{print $2}'); do
|
||||
if test -d "$mp"; then
|
||||
echo "# Using $mp as test fuse mount"
|
||||
FUSE_DIR="$mp"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if test "$(id -u)" = "0"; then
|
||||
is_uidzero=true
|
||||
else
|
||||
is_uidzero=false
|
||||
fi
|
||||
|
||||
# This is supposed to be an otherwise readable file in an unreadable (by the user) dir
|
||||
UNREADABLE=/root/.bashrc
|
||||
if "${is_uidzero}" || test -x "$(dirname "$UNREADABLE")"; then
|
||||
UNREADABLE=
|
||||
fi
|
||||
|
||||
# https://github.com/projectatomic/bubblewrap/issues/217
|
||||
# are we on a merged-/usr system?
|
||||
if [ /lib -ef /usr/lib ]; then
|
||||
BWRAP_RO_HOST_ARGS="--ro-bind /usr /usr
|
||||
--ro-bind /etc /etc
|
||||
--dir /var/tmp
|
||||
--symlink usr/lib /lib
|
||||
--symlink usr/lib64 /lib64
|
||||
--symlink usr/bin /bin
|
||||
--symlink usr/sbin /sbin
|
||||
--proc /proc
|
||||
--dev /dev"
|
||||
else
|
||||
BWRAP_RO_HOST_ARGS="--ro-bind /usr /usr
|
||||
--ro-bind /etc /etc
|
||||
--ro-bind /bin /bin
|
||||
--ro-bind-try /lib /lib
|
||||
--ro-bind-try /lib64 /lib64
|
||||
--ro-bind-try /sbin /sbin
|
||||
--ro-bind-try /nix/store /nix/store
|
||||
--dir /var/tmp
|
||||
--proc /proc
|
||||
--dev /dev"
|
||||
fi
|
||||
|
||||
# Default arg, bind whole host fs to /, tmpfs on /tmp
|
||||
RUN="${BWRAP} --bind / / --tmpfs /tmp"
|
||||
|
||||
if [ -z "${BWRAP_MUST_WORK-}" ] && ! $RUN true; then
|
||||
skip Seems like bwrap is not working at all. Maybe setuid is not working
|
||||
fi
|
||||
|
||||
extract_child_pid() {
|
||||
grep child-pid "$1" | sed "s/^.*: \([0-9]*\).*/\1/"
|
||||
}
|
||||
72
codex-rs/vendor/bubblewrap/tests/meson.build
vendored
Normal file
72
codex-rs/vendor/bubblewrap/tests/meson.build
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
test_programs = [
|
||||
['test-utils', executable(
|
||||
'test-utils',
|
||||
'test-utils.c',
|
||||
'../utils.c',
|
||||
'../utils.h',
|
||||
dependencies : [selinux_dep],
|
||||
include_directories : common_include_directories,
|
||||
)],
|
||||
]
|
||||
|
||||
executable(
|
||||
'try-syscall',
|
||||
'try-syscall.c',
|
||||
override_options: ['b_sanitize=none'],
|
||||
)
|
||||
|
||||
test_scripts = [
|
||||
'test-run.sh',
|
||||
'test-seccomp.py',
|
||||
'test-specifying-pidns.sh',
|
||||
'test-specifying-userns.sh',
|
||||
]
|
||||
|
||||
test_env = environment()
|
||||
test_env.set('BWRAP', bwrap.full_path())
|
||||
test_env.set('G_TEST_BUILDDIR', meson.current_build_dir() / '..')
|
||||
test_env.set('G_TEST_SRCDIR', meson.current_source_dir() / '..')
|
||||
|
||||
foreach pair : test_programs
|
||||
name = pair[0]
|
||||
test_program = pair[1]
|
||||
if meson.version().version_compare('>=0.50.0')
|
||||
test(
|
||||
name,
|
||||
test_program,
|
||||
env : test_env,
|
||||
protocol : 'tap',
|
||||
)
|
||||
else
|
||||
test(
|
||||
name,
|
||||
test_program,
|
||||
env : test_env,
|
||||
)
|
||||
endif
|
||||
endforeach
|
||||
|
||||
foreach test_script : test_scripts
|
||||
if test_script.endswith('.py')
|
||||
interpreter = python
|
||||
else
|
||||
interpreter = bash
|
||||
endif
|
||||
|
||||
if meson.version().version_compare('>=0.50.0')
|
||||
test(
|
||||
test_script,
|
||||
interpreter,
|
||||
args : [files(test_script)],
|
||||
env : test_env,
|
||||
protocol : 'tap',
|
||||
)
|
||||
else
|
||||
test(
|
||||
test_script,
|
||||
interpreter,
|
||||
args : [files(test_script)],
|
||||
env : test_env,
|
||||
)
|
||||
endif
|
||||
endforeach
|
||||
692
codex-rs/vendor/bubblewrap/tests/test-run.sh
vendored
Executable file
692
codex-rs/vendor/bubblewrap/tests/test-run.sh
vendored
Executable file
@@ -0,0 +1,692 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeuo pipefail
|
||||
|
||||
srcd=$(cd $(dirname "$0") && pwd)
|
||||
|
||||
. ${srcd}/libtest.sh
|
||||
|
||||
bn=$(basename "$0")
|
||||
|
||||
test_count=0
|
||||
ok () {
|
||||
test_count=$((test_count + 1))
|
||||
echo ok $test_count "$@"
|
||||
}
|
||||
ok_skip () {
|
||||
ok "# SKIP" "$@"
|
||||
}
|
||||
done_testing () {
|
||||
echo "1..$test_count"
|
||||
}
|
||||
|
||||
# Test help
|
||||
${BWRAP} --help > help.txt
|
||||
assert_file_has_content help.txt "usage: ${BWRAP}"
|
||||
ok "Help works"
|
||||
|
||||
for ALT in "" "--unshare-user-try" "--unshare-pid" "--unshare-user-try --unshare-pid"; do
|
||||
# Test fuse fs as bind source
|
||||
if [ "x$FUSE_DIR" != "x" ]; then
|
||||
$RUN $ALT --proc /proc --dev /dev --bind $FUSE_DIR /tmp/foo true
|
||||
ok "can bind-mount a FUSE directory with $ALT"
|
||||
else
|
||||
ok_skip "no FUSE support"
|
||||
fi
|
||||
# no --dev => no devpts => no map_root workaround
|
||||
$RUN $ALT --proc /proc true
|
||||
ok "can mount /proc with $ALT"
|
||||
# No network
|
||||
$RUN $ALT --unshare-net --proc /proc --dev /dev true
|
||||
ok "can unshare network, create new /dev with $ALT"
|
||||
# Unreadable file
|
||||
echo -n "expect EPERM: " >&2
|
||||
|
||||
# Test caps when bwrap is not setuid
|
||||
if test -n "${bwrap_is_suid:-}"; then
|
||||
CAP="--cap-add ALL"
|
||||
else
|
||||
CAP=""
|
||||
fi
|
||||
|
||||
if ! cat /etc/shadow >/dev/null &&
|
||||
$RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /tmp/foo; then
|
||||
assert_not_reached Could read /etc/shadow via /tmp/foo bind-mount
|
||||
fi
|
||||
|
||||
if ! cat /etc/shadow >/dev/null &&
|
||||
$RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /etc/shadow; then
|
||||
assert_not_reached Could read /etc/shadow
|
||||
fi
|
||||
|
||||
ok "cannot read /etc/shadow with $ALT"
|
||||
# Unreadable dir
|
||||
if [ "x$UNREADABLE" != "x" ]; then
|
||||
echo -n "expect EPERM: " >&2
|
||||
if $RUN $ALT --unshare-net --proc /proc --dev /dev --bind $UNREADABLE /tmp/foo cat /tmp/foo; then
|
||||
assert_not_reached Could read $UNREADABLE
|
||||
fi
|
||||
ok "cannot read $UNREADABLE with $ALT"
|
||||
else
|
||||
ok_skip "not sure what unreadable file to use"
|
||||
fi
|
||||
|
||||
# bind dest in symlink (https://github.com/projectatomic/bubblewrap/pull/119)
|
||||
$RUN $ALT --dir /tmp/dir --symlink dir /tmp/link --bind /etc /tmp/link true
|
||||
ok "can bind a destination over a symlink"
|
||||
done
|
||||
|
||||
# Test symlink behaviour
|
||||
rm -f ./symlink
|
||||
$RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/null "$(pwd)/symlink" true >&2
|
||||
readlink ./symlink > target.txt
|
||||
assert_file_has_content target.txt /dev/null
|
||||
ok "--symlink works"
|
||||
$RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/null "$(pwd)/symlink" true >&2
|
||||
ok "--symlink is idempotent"
|
||||
if $RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/full "$(pwd)/symlink" true 2>err.txt; then
|
||||
fatal "creating a conflicting symlink should have failed"
|
||||
else
|
||||
assert_file_has_content err.txt "Can't make symlink .*: existing destination is /dev/null"
|
||||
fi
|
||||
ok "--symlink doesn't overwrite a conflicting symlink"
|
||||
|
||||
# Test devices
|
||||
$RUN --unshare-pid --dev /dev ls -al /dev/{stdin,stdout,stderr,null,random,urandom,fd,core} >/dev/null
|
||||
ok "all expected devices were created"
|
||||
|
||||
# Test --as-pid-1
|
||||
$RUN --unshare-pid --as-pid-1 --bind / / bash -c 'echo $$' > as_pid_1.txt
|
||||
assert_file_has_content as_pid_1.txt "1"
|
||||
ok "can run as pid 1"
|
||||
|
||||
# Test --info-fd and --json-status-fd
|
||||
if $RUN --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'exit 42' 42>info.json 43>json-status.json 2>err.txt; then
|
||||
fatal "should have been exit 42"
|
||||
fi
|
||||
assert_file_has_content info.json '"child-pid": [0-9]'
|
||||
assert_file_has_content json-status.json '"child-pid": [0-9]'
|
||||
assert_file_has_content_literal json-status.json '"exit-code": 42'
|
||||
ok "info and json-status fd"
|
||||
|
||||
DATA=$($RUN --proc /proc --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'stat -L -c "%n %i" /proc/self/ns/*' 42>info.json 43>json-status.json 2>err.txt)
|
||||
|
||||
for NS in "ipc" "mnt" "net" "pid" "uts"; do
|
||||
|
||||
want=$(echo "$DATA" | grep "/proc/self/ns/$NS" | awk '{print $2}')
|
||||
assert_file_has_content info.json "$want"
|
||||
assert_file_has_content json-status.json "$want"
|
||||
done
|
||||
|
||||
ok "namespace id info in info and json-status fd"
|
||||
|
||||
if ! command -v strace >/dev/null || ! strace -h | grep -v -e default | grep -e fault >/dev/null; then
|
||||
ok_skip "no strace fault injection"
|
||||
else
|
||||
! strace -o /dev/null -f -e trace=prctl -e fault=prctl:when=39 $RUN --die-with-parent --json-status-fd 42 true 42>json-status.json
|
||||
assert_not_file_has_content json-status.json '"exit-code": [0-9]'
|
||||
ok "pre-exec failure doesn't include exit-code in json-status"
|
||||
fi
|
||||
|
||||
notanexecutable=/
|
||||
$RUN --json-status-fd 42 $notanexecutable 42>json-status.json || true
|
||||
assert_not_file_has_content json-status.json '"exit-code": [0-9]'
|
||||
ok "exec failure doesn't include exit-code in json-status"
|
||||
|
||||
# These tests require --unshare-user
|
||||
if test -n "${bwrap_is_suid:-}"; then
|
||||
ok_skip "no --cap-add support"
|
||||
ok_skip "no --cap-add support"
|
||||
ok_skip "no --disable-userns"
|
||||
else
|
||||
BWRAP_RECURSE="$BWRAP --unshare-user --uid 0 --gid 0 --cap-add ALL --bind / / --bind /proc /proc"
|
||||
|
||||
# $BWRAP May be inaccessible due to the user namespace so use /proc/self/exe
|
||||
$BWRAP_RECURSE -- /proc/self/exe --unshare-all --bind / / --bind /proc /proc echo hello > recursive_proc.txt
|
||||
assert_file_has_content recursive_proc.txt "hello"
|
||||
ok "can mount /proc recursively"
|
||||
|
||||
$BWRAP_RECURSE -- /proc/self/exe --unshare-all ${BWRAP_RO_HOST_ARGS} findmnt > recursive-newroot.txt
|
||||
assert_file_has_content recursive-newroot.txt "/usr"
|
||||
ok "can pivot to new rootfs recursively"
|
||||
|
||||
$BWRAP --dev-bind / / -- true
|
||||
! $BWRAP --assert-userns-disabled --dev-bind / / -- true
|
||||
$BWRAP --unshare-user --disable-userns --dev-bind / / -- true
|
||||
! $BWRAP --unshare-user --disable-userns --dev-bind / / -- $BWRAP --dev-bind / / -- true
|
||||
$BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 2 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true"
|
||||
$BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 100 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true"
|
||||
$BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "! $BWRAP --unshare-user --dev-bind / / --assert-userns-disabled -- true"
|
||||
|
||||
$BWRAP_RECURSE --dev-bind / / -- true
|
||||
! $BWRAP_RECURSE --assert-userns-disabled --dev-bind / / -- true
|
||||
$BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- true
|
||||
! $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- /proc/self/exe --dev-bind / / -- true
|
||||
$BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 2 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true"
|
||||
$BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 100 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true"
|
||||
$BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "! $BWRAP --unshare-user --dev-bind / / --assert-userns-disabled -- true"
|
||||
|
||||
ok "can disable nested userns"
|
||||
fi
|
||||
|
||||
# Test error prefixing
|
||||
if $RUN --unshare-pid --bind /source-enoent /dest true 2>err.txt; then
|
||||
assert_not_reached "bound nonexistent source"
|
||||
fi
|
||||
assert_file_has_content err.txt "^bwrap: Can't find source path.*source-enoent"
|
||||
ok "error prefixing"
|
||||
|
||||
if ! ${is_uidzero}; then
|
||||
# When invoked as non-root, check that by default we have no caps left
|
||||
for OPT in "" "--unshare-user-try --as-pid-1" "--unshare-user-try" "--as-pid-1"; do
|
||||
e=0
|
||||
$RUN $OPT --unshare-pid getpcaps 1 >&2 2> caps.test || e=$?
|
||||
sed -e 's/^/# /' < caps.test >&2
|
||||
test "$e" = 0
|
||||
assert_not_file_has_content caps.test ': =.*cap'
|
||||
done
|
||||
ok "we have no caps as uid != 0"
|
||||
else
|
||||
capsh --print | sed -e 's/no-new-privs=0/no-new-privs=1/' > caps.expected
|
||||
|
||||
for OPT in "" "--as-pid-1"; do
|
||||
$RUN $OPT --unshare-pid capsh --print >caps.test
|
||||
diff -u caps.expected caps.test
|
||||
done
|
||||
# And test that we can drop all, as well as specific caps
|
||||
$RUN $OPT --cap-drop ALL --unshare-pid capsh --print >caps.test
|
||||
assert_file_has_content caps.test 'Current: =$'
|
||||
# Check for dropping kill/fowner (we assume all uid 0 callers have this)
|
||||
# But we should still have net_bind_service for example
|
||||
$RUN $OPT --cap-drop CAP_KILL --cap-drop CAP_FOWNER --unshare-pid capsh --print >caps.test
|
||||
# capsh's output format changed from v2.29 -> drops are now indicated with -eip
|
||||
if grep 'Current: =.*+eip$' caps.test; then
|
||||
assert_not_file_has_content caps.test '^Current: =.*cap_kill.*+eip$'
|
||||
assert_not_file_has_content caps.test '^Current: =.*cap_fowner.*+eip$'
|
||||
assert_file_has_content caps.test '^Current: =.*cap_net_bind_service.*+eip$'
|
||||
else
|
||||
assert_file_has_content caps.test '^Current: =eip.*cap_kill.*-eip$'
|
||||
assert_file_has_content caps.test '^Current: =eip.*cap_fowner.*-eip$'
|
||||
assert_not_file_has_content caps.test '^Current: =.*cap_net_bind_service.*-eip$'
|
||||
fi
|
||||
ok "we have the expected caps as uid 0"
|
||||
fi
|
||||
|
||||
# Test --die-with-parent
|
||||
|
||||
cat >lockf-n.py <<EOF
|
||||
#!/usr/bin/env python3
|
||||
import struct,fcntl,sys
|
||||
path = sys.argv[1]
|
||||
if sys.argv[2] == 'wait':
|
||||
locktype = fcntl.F_SETLKW
|
||||
else:
|
||||
locktype = fcntl.F_SETLK
|
||||
lockdata = struct.pack("hhqqhh", fcntl.F_WRLCK, 0, 0, 0, 0, 0)
|
||||
fd=open(sys.argv[1], 'a')
|
||||
try:
|
||||
fcntl.fcntl(fd.fileno(), locktype, lockdata)
|
||||
except IOError as e:
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
EOF
|
||||
chmod a+x lockf-n.py
|
||||
touch lock
|
||||
|
||||
for die_with_parent_argv in "--die-with-parent" "--die-with-parent --unshare-pid"; do
|
||||
# We have to loop here, because bwrap doesn't wait for the lock if
|
||||
# another process is holding it. If we're unlucky, lockf-n.py will
|
||||
# be holding it.
|
||||
bash -c "while true; do $RUN ${die_with_parent_argv} --lock-file $(pwd)/lock sleep 1h; done" &
|
||||
childshellpid=$!
|
||||
|
||||
# Wait for lock to be taken (yes hacky)
|
||||
for x in $(seq 10); do
|
||||
if ./lockf-n.py ./lock nowait; then
|
||||
sleep 1
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
if ./lockf-n.py ./lock nowait; then
|
||||
assert_not_reached "timed out waiting for lock"
|
||||
fi
|
||||
|
||||
# Kill the shell, which should kill bwrap (and the sleep)
|
||||
kill -9 ${childshellpid}
|
||||
# Lock file should be unlocked
|
||||
./lockf-n.py ./lock wait
|
||||
ok "die with parent ${die_with_parent_argv}"
|
||||
done
|
||||
|
||||
printf '%s--dir\0/tmp/hello/world\0' '' > test.args
|
||||
printf '%s--dir\0/tmp/hello/world2\0' '' > test.args2
|
||||
printf '%s--dir\0/tmp/hello/world3\0' '' > test.args3
|
||||
$RUN --args 3 --args 4 --args 5 /bin/sh -c 'test -d /tmp/hello/world && test -d /tmp/hello/world2 && test -d /tmp/hello/world3' 3<test.args 4<test.args2 5<test.args3
|
||||
ok "we can parse arguments from a fd"
|
||||
|
||||
mkdir bin
|
||||
echo "#!/bin/sh" > bin/--inadvisable-executable-name--
|
||||
echo "echo hello" >> bin/--inadvisable-executable-name--
|
||||
chmod +x bin/--inadvisable-executable-name--
|
||||
PATH="${srcd}:$PATH" $RUN -- sh -c "echo hello" > stdout
|
||||
assert_file_has_content stdout hello
|
||||
ok "we can run with --"
|
||||
PATH="$(pwd)/bin:$PATH" $RUN -- --inadvisable-executable-name-- > stdout
|
||||
assert_file_has_content stdout hello
|
||||
ok "we can run an inadvisable executable name with --"
|
||||
if $RUN -- --dev-bind /dev /dev sh -c 'echo should not have run'; then
|
||||
assert_not_reached "'--dev-bind' should have been interpreted as a (silly) executable name"
|
||||
fi
|
||||
ok "options like --dev-bind are defanged by --"
|
||||
|
||||
if command -v mktemp > /dev/null; then
|
||||
tempfile="$(mktemp /tmp/bwrap-test-XXXXXXXX)"
|
||||
echo "hello" > "$tempfile"
|
||||
$BWRAP --bind / / cat "$tempfile" > stdout
|
||||
assert_file_has_content stdout hello
|
||||
ok "bind-mount of / exposes real /tmp"
|
||||
$BWRAP --bind / / --bind /tmp /tmp cat "$tempfile" > stdout
|
||||
assert_file_has_content stdout hello
|
||||
ok "bind-mount of /tmp exposes real /tmp"
|
||||
if [ -d /mnt ] && [ ! -L /mnt ]; then
|
||||
$BWRAP --bind / / --bind /tmp /mnt cat "/mnt/${tempfile#/tmp/}" > stdout
|
||||
assert_file_has_content stdout hello
|
||||
ok "bind-mount of /tmp onto /mnt exposes real /tmp"
|
||||
else
|
||||
ok_skip "/mnt does not exist or is a symlink"
|
||||
fi
|
||||
else
|
||||
ok_skip "mktemp not found"
|
||||
ok_skip "mktemp not found"
|
||||
ok_skip "mktemp not found"
|
||||
fi
|
||||
|
||||
if $RUN test -d /tmp/oldroot; then
|
||||
assert_not_reached "/tmp/oldroot should not be visible"
|
||||
fi
|
||||
if $RUN test -d /tmp/newroot; then
|
||||
assert_not_reached "/tmp/newroot should not be visible"
|
||||
fi
|
||||
|
||||
echo "hello" > input.$$
|
||||
$BWRAP --bind / / --bind "$(pwd)" /tmp cat /tmp/input.$$ > stdout
|
||||
assert_file_has_content stdout hello
|
||||
if $BWRAP --bind / / --bind "$(pwd)" /tmp test -d /tmp/oldroot; then
|
||||
assert_not_reached "/tmp/oldroot should not be visible"
|
||||
fi
|
||||
if $BWRAP --bind / / --bind "$(pwd)" /tmp test -d /tmp/newroot; then
|
||||
assert_not_reached "/tmp/newroot should not be visible"
|
||||
fi
|
||||
ok "we can mount another directory onto /tmp"
|
||||
|
||||
echo "hello" > input.$$
|
||||
$RUN --bind "$(pwd)" /tmp/here cat /tmp/here/input.$$ > stdout
|
||||
assert_file_has_content stdout hello
|
||||
if $RUN --bind "$(pwd)" /tmp/here test -d /tmp/oldroot; then
|
||||
assert_not_reached "/tmp/oldroot should not be visible"
|
||||
fi
|
||||
if $RUN --bind "$(pwd)" /tmp/here test -d /tmp/newroot; then
|
||||
assert_not_reached "/tmp/newroot should not be visible"
|
||||
fi
|
||||
ok "we can mount another directory inside /tmp"
|
||||
|
||||
touch some-file
|
||||
mkdir -p some-dir
|
||||
rm -fr new-dir-mountpoint
|
||||
rm -fr new-file-mountpoint
|
||||
$RUN \
|
||||
--bind "$(pwd -P)/some-dir" "$(pwd -P)/new-dir-mountpoint" \
|
||||
--bind "$(pwd -P)/some-file" "$(pwd -P)/new-file-mountpoint" \
|
||||
true
|
||||
command stat -c '%a' new-dir-mountpoint > new-dir-permissions
|
||||
assert_file_has_content new-dir-permissions 755
|
||||
command stat -c '%a' new-file-mountpoint > new-file-permissions
|
||||
assert_file_has_content new-file-permissions 444
|
||||
ok "Files and directories created as mount points have expected permissions"
|
||||
|
||||
|
||||
if [ -S /dev/log ]; then
|
||||
$RUN --bind / / --bind "$(realpath /dev/log)" "$(realpath /dev/log)" true
|
||||
ok "Can bind-mount a socket (/dev/log) onto a socket"
|
||||
else
|
||||
ok_skip "- /dev/log is not a socket, cannot test bubblewrap#409"
|
||||
fi
|
||||
|
||||
mkdir -p dir-already-existed
|
||||
chmod 0710 dir-already-existed
|
||||
mkdir -p dir-already-existed2
|
||||
chmod 0754 dir-already-existed2
|
||||
rm -fr new-dir-default-perms
|
||||
rm -fr new-dir-set-perms
|
||||
$RUN \
|
||||
--perms 1741 --dir "$(pwd -P)/new-dir-set-perms" \
|
||||
--dir "$(pwd -P)/dir-already-existed" \
|
||||
--perms 0741 --dir "$(pwd -P)/dir-already-existed2" \
|
||||
--dir "$(pwd -P)/dir-chmod" \
|
||||
--chmod 1755 "$(pwd -P)/dir-chmod" \
|
||||
--dir "$(pwd -P)/new-dir-default-perms" \
|
||||
true
|
||||
command stat -c '%a' new-dir-default-perms > new-dir-permissions
|
||||
assert_file_has_content new-dir-permissions '^755$'
|
||||
command stat -c '%a' new-dir-set-perms > new-dir-permissions
|
||||
assert_file_has_content new-dir-permissions '^1741$'
|
||||
command stat -c '%a' dir-already-existed > dir-permissions
|
||||
assert_file_has_content dir-permissions '^710$'
|
||||
command stat -c '%a' dir-already-existed2 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^754$'
|
||||
command stat -c '%a' dir-chmod > dir-permissions
|
||||
assert_file_has_content dir-permissions '^1755$'
|
||||
ok "Directories created explicitly have expected permissions"
|
||||
|
||||
rm -fr parent
|
||||
rm -fr parent-of-1777
|
||||
rm -fr parent-of-0755
|
||||
rm -fr parent-of-0644
|
||||
rm -fr parent-of-0750
|
||||
rm -fr parent-of-0710
|
||||
rm -fr parent-of-0720
|
||||
rm -fr parent-of-0640
|
||||
rm -fr parent-of-0700
|
||||
rm -fr parent-of-0600
|
||||
rm -fr parent-of-0705
|
||||
rm -fr parent-of-0604
|
||||
rm -fr parent-of-0000
|
||||
$RUN \
|
||||
--dir "$(pwd -P)"/parent/dir \
|
||||
--perms 1777 --dir "$(pwd -P)"/parent-of-1777/dir \
|
||||
--perms 0755 --dir "$(pwd -P)"/parent-of-0755/dir \
|
||||
--perms 0644 --dir "$(pwd -P)"/parent-of-0644/dir \
|
||||
--perms 0750 --dir "$(pwd -P)"/parent-of-0750/dir \
|
||||
--perms 0710 --dir "$(pwd -P)"/parent-of-0710/dir \
|
||||
--perms 0720 --dir "$(pwd -P)"/parent-of-0720/dir \
|
||||
--perms 0640 --dir "$(pwd -P)"/parent-of-0640/dir \
|
||||
--perms 0700 --dir "$(pwd -P)"/parent-of-0700/dir \
|
||||
--perms 0600 --dir "$(pwd -P)"/parent-of-0600/dir \
|
||||
--perms 0705 --dir "$(pwd -P)"/parent-of-0705/dir \
|
||||
--perms 0604 --dir "$(pwd -P)"/parent-of-0604/dir \
|
||||
--perms 0000 --dir "$(pwd -P)"/parent-of-0000/dir \
|
||||
true
|
||||
command stat -c '%a' parent > dir-permissions
|
||||
assert_file_has_content dir-permissions '^755$'
|
||||
command stat -c '%a' parent-of-1777 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^755$'
|
||||
command stat -c '%a' parent-of-0755 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^755$'
|
||||
command stat -c '%a' parent-of-0644 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^755$'
|
||||
command stat -c '%a' parent-of-0750 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^750$'
|
||||
command stat -c '%a' parent-of-0710 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^750$'
|
||||
command stat -c '%a' parent-of-0720 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^750$'
|
||||
command stat -c '%a' parent-of-0640 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^750$'
|
||||
command stat -c '%a' parent-of-0700 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^700$'
|
||||
command stat -c '%a' parent-of-0600 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^700$'
|
||||
command stat -c '%a' parent-of-0705 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^705$'
|
||||
command stat -c '%a' parent-of-0604 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^705$'
|
||||
command stat -c '%a' parent-of-0000 > dir-permissions
|
||||
assert_file_has_content dir-permissions '^700$'
|
||||
chmod -R 0700 parent*
|
||||
rm -fr parent*
|
||||
ok "Directories created as parents have expected permissions"
|
||||
|
||||
$RUN \
|
||||
--perms 01777 --tmpfs "$(pwd -P)" \
|
||||
cat /proc/self/mountinfo >&2
|
||||
$RUN \
|
||||
--perms 01777 --tmpfs "$(pwd -P)" \
|
||||
stat -c '%a' "$(pwd -P)" > dir-permissions
|
||||
assert_file_has_content dir-permissions '^1777$'
|
||||
$RUN \
|
||||
--tmpfs "$(pwd -P)" \
|
||||
stat -c '%a' "$(pwd -P)" > dir-permissions
|
||||
assert_file_has_content dir-permissions '^755$'
|
||||
ok "tmpfs has expected permissions"
|
||||
|
||||
# 1048576 = 1 MiB
|
||||
if test -n "${bwrap_is_suid:-}"; then
|
||||
if $RUN --size 1048576 --tmpfs "$(pwd -P)" true; then
|
||||
assert_not_reached "Should not allow --size --tmpfs when setuid"
|
||||
fi
|
||||
ok "--size --tmpfs is not allowed when setuid"
|
||||
elif df --output=size --block-size=1K "$(pwd -P)" >/dev/null 2>/dev/null; then
|
||||
$RUN \
|
||||
--size 1048576 --tmpfs "$(pwd -P)" \
|
||||
df --output=size --block-size=1K "$(pwd -P)" > dir-size
|
||||
assert_file_has_content dir-size '^ *1024$'
|
||||
$RUN \
|
||||
--size 1048576 --perms 01777 --tmpfs "$(pwd -P)" \
|
||||
stat -c '%a' "$(pwd -P)" > dir-permissions
|
||||
assert_file_has_content dir-permissions '^1777$'
|
||||
$RUN \
|
||||
--size 1048576 --perms 01777 --tmpfs "$(pwd -P)" \
|
||||
df --output=size --block-size=1K "$(pwd -P)" > dir-size
|
||||
assert_file_has_content dir-size '^ *1024$'
|
||||
$RUN \
|
||||
--perms 01777 --size 1048576 --tmpfs "$(pwd -P)" \
|
||||
stat -c '%a' "$(pwd -P)" > dir-permissions
|
||||
assert_file_has_content dir-permissions '^1777$'
|
||||
$RUN \
|
||||
--perms 01777 --size 1048576 --tmpfs "$(pwd -P)" \
|
||||
df --output=size --block-size=1K "$(pwd -P)" > dir-size
|
||||
assert_file_has_content dir-size '^ *1024$'
|
||||
ok "tmpfs has expected size"
|
||||
else
|
||||
$RUN --size 1048576 --tmpfs "$(pwd -P)" true
|
||||
$RUN --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" true
|
||||
$RUN --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" true
|
||||
ok_skip "df is too old, cannot test --size --tmpfs fully"
|
||||
fi
|
||||
|
||||
$RUN \
|
||||
--file 0 /tmp/file \
|
||||
stat -c '%a' /tmp/file < /dev/null > file-permissions
|
||||
assert_file_has_content file-permissions '^666$'
|
||||
$RUN \
|
||||
--perms 0640 --file 0 /tmp/file \
|
||||
stat -c '%a' /tmp/file < /dev/null > file-permissions
|
||||
assert_file_has_content file-permissions '^640$'
|
||||
$RUN \
|
||||
--bind-data 0 /tmp/file \
|
||||
stat -c '%a' /tmp/file < /dev/null > file-permissions
|
||||
assert_file_has_content file-permissions '^600$'
|
||||
$RUN \
|
||||
--perms 0640 --bind-data 0 /tmp/file \
|
||||
stat -c '%a' /tmp/file < /dev/null > file-permissions
|
||||
assert_file_has_content file-permissions '^640$'
|
||||
$RUN \
|
||||
--ro-bind-data 0 /tmp/file \
|
||||
stat -c '%a' /tmp/file < /dev/null > file-permissions
|
||||
assert_file_has_content file-permissions '^600$'
|
||||
$RUN \
|
||||
--perms 0640 --ro-bind-data 0 /tmp/file \
|
||||
stat -c '%a' /tmp/file < /dev/null > file-permissions
|
||||
assert_file_has_content file-permissions '^640$'
|
||||
ok "files have expected permissions"
|
||||
|
||||
if $RUN --size 0 --tmpfs /tmp/a true; then
|
||||
assert_not_reached Zero tmpfs size allowed
|
||||
fi
|
||||
if $RUN --size 123bogus --tmpfs /tmp/a true; then
|
||||
assert_not_reached Bogus tmpfs size allowed
|
||||
fi
|
||||
if $RUN --size '' --tmpfs /tmp/a true; then
|
||||
assert_not_reached Empty tmpfs size allowed
|
||||
fi
|
||||
if $RUN --size -12345678 --tmpfs /tmp/a true; then
|
||||
assert_not_reached Negative tmpfs size allowed
|
||||
fi
|
||||
if $RUN --size ' -12345678' --tmpfs /tmp/a true; then
|
||||
assert_not_reached Negative tmpfs size with space allowed
|
||||
fi
|
||||
# This is 2^64
|
||||
if $RUN --size 18446744073709551616 --tmpfs /tmp/a true; then
|
||||
assert_not_reached Overflowing tmpfs size allowed
|
||||
fi
|
||||
# This is 2^63 + 1; note that the current max size is SIZE_MAX/2
|
||||
if $RUN --size 9223372036854775809 --tmpfs /tmp/a true; then
|
||||
assert_not_reached Too-large tmpfs size allowed
|
||||
fi
|
||||
ok "bogus tmpfs size not allowed"
|
||||
|
||||
if $RUN --perms 0640 --perms 0640 --tmpfs /tmp/a true; then
|
||||
assert_not_reached Multiple perms options allowed
|
||||
fi
|
||||
if $RUN --size 1048576 --size 1048576 --tmpfs /tmp/a true; then
|
||||
assert_not_reached Multiple perms options allowed
|
||||
fi
|
||||
ok "--perms and --size only allowed once"
|
||||
|
||||
|
||||
FOO= BAR=baz $RUN --setenv FOO bar sh -c 'echo "$FOO$BAR"' > stdout
|
||||
assert_file_has_content stdout barbaz
|
||||
FOO=wrong BAR=baz $RUN --setenv FOO bar sh -c 'echo "$FOO$BAR"' > stdout
|
||||
assert_file_has_content stdout barbaz
|
||||
FOO=wrong BAR=baz $RUN --unsetenv FOO sh -c 'printf "%s%s" "$FOO" "$BAR"' > stdout
|
||||
printf baz > reference
|
||||
assert_files_equal stdout reference
|
||||
FOO=wrong BAR=wrong $RUN --clearenv /usr/bin/env > stdout
|
||||
echo "PWD=$(pwd -P)" > reference
|
||||
assert_files_equal stdout reference
|
||||
ok "environment manipulation"
|
||||
|
||||
$RUN sh -c 'echo $0' > stdout
|
||||
assert_file_has_content stdout sh
|
||||
$RUN --argv0 sh sh -c 'echo $0' > stdout
|
||||
assert_file_has_content stdout sh
|
||||
$RUN --argv0 right sh -c 'echo $0' > stdout
|
||||
assert_file_has_content stdout right
|
||||
ok "argv0 manipulation"
|
||||
|
||||
echo "foobar" > file-data
|
||||
$RUN --proc /proc --dev /dev --bind / / --bind-fd 100 /tmp cat /tmp/file-data 100< . > stdout
|
||||
assert_file_has_content stdout foobar
|
||||
|
||||
ok "bind-fd"
|
||||
|
||||
$RUN --chdir / --chdir / true > stdout 2>&1
|
||||
assert_file_has_content stdout '^bwrap: Only the last --chdir option will take effect$'
|
||||
ok "warning logged for redundant --chdir"
|
||||
|
||||
$RUN --level-prefix --chdir / --chdir / true > stdout 2>&1
|
||||
assert_file_has_content stdout '^<4>bwrap: Only the last --chdir option will take effect$'
|
||||
ok "--level-prefix"
|
||||
|
||||
if test -n "${bwrap_is_suid:-}"; then
|
||||
ok_skip "no --overlay support"
|
||||
ok_skip "no --overlay support"
|
||||
ok_skip "no --tmp-overlay support"
|
||||
ok_skip "no --ro-overlay support"
|
||||
ok_skip "no --overlay-src support"
|
||||
else
|
||||
mkdir lower1 lower2 upper work
|
||||
printf 1 > lower1/a
|
||||
printf 2 > lower1/b
|
||||
printf 3 > lower2/b
|
||||
printf 4 > upper/a
|
||||
|
||||
# Check if unprivileged overlayfs is available
|
||||
if ! unshare -rm mount -t overlay -o lowerdir=lower1,upperdir=upper,workdir=work,userxattr overlay lower2; then
|
||||
ok_skip "no kernel support for unprivileged overlayfs"
|
||||
ok_skip "no kernel support for unprivileged overlayfs"
|
||||
ok_skip "no kernel support for unprivileged overlayfs"
|
||||
ok_skip "no kernel support for unprivileged overlayfs"
|
||||
ok_skip "no kernel support for unprivileged overlayfs"
|
||||
else
|
||||
|
||||
# Test --overlay
|
||||
if $RUN --overlay upper work /tmp true 2>err.txt; then
|
||||
assert_not_reached At least one --overlay-src not required
|
||||
fi
|
||||
assert_file_has_content err.txt "^bwrap: --overlay requires at least one --overlay-src"
|
||||
$RUN --overlay-src lower1 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/a > stdout
|
||||
assert_file_has_content stdout '^4$'
|
||||
$RUN --overlay-src lower1 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/b > stdout
|
||||
assert_file_has_content stdout '^2$'
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/a > stdout
|
||||
assert_file_has_content stdout '^4$'
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/b > stdout
|
||||
assert_file_has_content stdout '^3$'
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z sh -c 'printf 5 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout
|
||||
assert_file_has_content stdout '^5$'
|
||||
assert_file_has_content upper/b '^5$'
|
||||
ok "--overlay"
|
||||
|
||||
# Test --overlay path escaping
|
||||
# Coincidentally, ":,\ is the face I make contemplating anyone who might
|
||||
# need this functionality, not that that's going to stop me from supporting
|
||||
# it.
|
||||
mkdir 'lower ":,\' 'upper ":,\' 'work ":,\'
|
||||
printf 1 > 'lower ":,\'/a
|
||||
$RUN --overlay-src 'lower ":,\' --overlay 'upper ":,\' 'work ":,\' /tmp/x sh -c 'cat /tmp/x/a; printf 2 > /tmp/x/a; cat /tmp/x/a' > stdout
|
||||
assert_file_has_content stdout '^12$'
|
||||
assert_file_has_content 'lower ":,\'/a '^1$'
|
||||
assert_file_has_content 'upper ":,\'/a '^2$'
|
||||
ok "--overlay path escaping"
|
||||
|
||||
# Test --tmp-overlay
|
||||
printf 1 > lower1/a
|
||||
printf 2 > lower1/b
|
||||
printf 3 > lower2/b
|
||||
if $RUN --tmp-overlay /tmp true 2>err.txt; then
|
||||
assert_not_reached At least one --overlay-src not required
|
||||
fi
|
||||
assert_file_has_content err.txt "^bwrap: --tmp-overlay requires at least one --overlay-src"
|
||||
$RUN --overlay-src lower1 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout
|
||||
assert_file_has_content stdout '^1$'
|
||||
$RUN --overlay-src lower1 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout
|
||||
assert_file_has_content stdout '^2$'
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout
|
||||
assert_file_has_content stdout '^1$'
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout
|
||||
assert_file_has_content stdout '^3$'
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z sh -c 'printf 4 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout
|
||||
assert_file_has_content stdout '^4$'
|
||||
$RUN --overlay-src lower1 --tmp-overlay /tmp/x --overlay-src lower2 --tmp-overlay /tmp/y sh -c 'cat /tmp/x/b; printf 4 > /tmp/x/b; cat /tmp/x/b; cat /tmp/y/b' > stdout
|
||||
assert_file_has_content stdout '^243$'
|
||||
assert_file_has_content lower1/b '^2$'
|
||||
assert_file_has_content lower2/b '^3$'
|
||||
ok "--tmp-overlay"
|
||||
|
||||
# Test --ro-overlay
|
||||
printf 1 > lower1/a
|
||||
printf 2 > lower1/b
|
||||
printf 3 > lower2/b
|
||||
if $RUN --ro-overlay /tmp true 2>err.txt; then
|
||||
assert_not_reached At least two --overlay-src not required
|
||||
fi
|
||||
assert_file_has_content err.txt "^bwrap: --ro-overlay requires at least two --overlay-src"
|
||||
if $RUN --overlay-src lower1 --ro-overlay /tmp true 2>err.txt; then
|
||||
assert_not_reached At least two --overlay-src not required
|
||||
fi
|
||||
assert_file_has_content err.txt "^bwrap: --ro-overlay requires at least two --overlay-src"
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout
|
||||
assert_file_has_content stdout '^1$'
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout
|
||||
assert_file_has_content stdout '^3$'
|
||||
$RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z sh -c 'printf 4 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout
|
||||
assert_file_has_content stdout '^3$'
|
||||
ok "--ro-overlay"
|
||||
|
||||
# Test --overlay-src restrictions
|
||||
if $RUN --overlay-src /tmp true 2>err.txt; then
|
||||
assert_not_reached Trailing --overlay-src allowed
|
||||
fi
|
||||
assert_file_has_content err.txt "^bwrap: --overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay"
|
||||
if $RUN --overlay-src /tmp --chdir / true 2>err.txt; then
|
||||
assert_not_reached --overlay-src allowed to precede non-overlay options
|
||||
fi
|
||||
assert_file_has_content err.txt "^bwrap: --overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay"
|
||||
ok "--overlay-src restrictions"
|
||||
|
||||
fi
|
||||
fi
|
||||
|
||||
done_testing
|
||||
635
codex-rs/vendor/bubblewrap/tests/test-seccomp.py
vendored
Executable file
635
codex-rs/vendor/bubblewrap/tests/test-seccomp.py
vendored
Executable file
@@ -0,0 +1,635 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2021 Simon McVittie
|
||||
# SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import termios
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import seccomp
|
||||
except ImportError:
|
||||
print('1..0 # SKIP cannot import seccomp Python module')
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# This is the @default set from systemd as of 2021-10-11
|
||||
DEFAULT_SET = set('''
|
||||
brk
|
||||
cacheflush
|
||||
clock_getres
|
||||
clock_getres_time64
|
||||
clock_gettime
|
||||
clock_gettime64
|
||||
clock_nanosleep
|
||||
clock_nanosleep_time64
|
||||
execve
|
||||
exit
|
||||
exit_group
|
||||
futex
|
||||
futex_time64
|
||||
get_robust_list
|
||||
get_thread_area
|
||||
getegid
|
||||
getegid32
|
||||
geteuid
|
||||
geteuid32
|
||||
getgid
|
||||
getgid32
|
||||
getgroups
|
||||
getgroups32
|
||||
getpgid
|
||||
getpgrp
|
||||
getpid
|
||||
getppid
|
||||
getrandom
|
||||
getresgid
|
||||
getresgid32
|
||||
getresuid
|
||||
getresuid32
|
||||
getrlimit
|
||||
getsid
|
||||
gettid
|
||||
gettimeofday
|
||||
getuid
|
||||
getuid32
|
||||
membarrier
|
||||
mmap
|
||||
mmap2
|
||||
munmap
|
||||
nanosleep
|
||||
pause
|
||||
prlimit64
|
||||
restart_syscall
|
||||
rseq
|
||||
rt_sigreturn
|
||||
sched_getaffinity
|
||||
sched_yield
|
||||
set_robust_list
|
||||
set_thread_area
|
||||
set_tid_address
|
||||
set_tls
|
||||
sigreturn
|
||||
time
|
||||
ugetrlimit
|
||||
'''.split())
|
||||
|
||||
# This is the @basic-io set from systemd
|
||||
BASIC_IO_SET = set('''
|
||||
_llseek
|
||||
close
|
||||
close_range
|
||||
dup
|
||||
dup2
|
||||
dup3
|
||||
lseek
|
||||
pread64
|
||||
preadv
|
||||
preadv2
|
||||
pwrite64
|
||||
pwritev
|
||||
pwritev2
|
||||
read
|
||||
readv
|
||||
write
|
||||
writev
|
||||
'''.split())
|
||||
|
||||
# This is the @filesystem-io set from systemd
|
||||
FILESYSTEM_SET = set('''
|
||||
access
|
||||
chdir
|
||||
chmod
|
||||
close
|
||||
creat
|
||||
faccessat
|
||||
faccessat2
|
||||
fallocate
|
||||
fchdir
|
||||
fchmod
|
||||
fchmodat
|
||||
fcntl
|
||||
fcntl64
|
||||
fgetxattr
|
||||
flistxattr
|
||||
fremovexattr
|
||||
fsetxattr
|
||||
fstat
|
||||
fstat64
|
||||
fstatat64
|
||||
fstatfs
|
||||
fstatfs64
|
||||
ftruncate
|
||||
ftruncate64
|
||||
futimesat
|
||||
getcwd
|
||||
getdents
|
||||
getdents64
|
||||
getxattr
|
||||
inotify_add_watch
|
||||
inotify_init
|
||||
inotify_init1
|
||||
inotify_rm_watch
|
||||
lgetxattr
|
||||
link
|
||||
linkat
|
||||
listxattr
|
||||
llistxattr
|
||||
lremovexattr
|
||||
lsetxattr
|
||||
lstat
|
||||
lstat64
|
||||
mkdir
|
||||
mkdirat
|
||||
mknod
|
||||
mknodat
|
||||
newfstatat
|
||||
oldfstat
|
||||
oldlstat
|
||||
oldstat
|
||||
open
|
||||
openat
|
||||
openat2
|
||||
readlink
|
||||
readlinkat
|
||||
removexattr
|
||||
rename
|
||||
renameat
|
||||
renameat2
|
||||
rmdir
|
||||
setxattr
|
||||
stat
|
||||
stat64
|
||||
statfs
|
||||
statfs64
|
||||
statx
|
||||
symlink
|
||||
symlinkat
|
||||
truncate
|
||||
truncate64
|
||||
unlink
|
||||
unlinkat
|
||||
utime
|
||||
utimensat
|
||||
utimensat_time64
|
||||
utimes
|
||||
'''.split())
|
||||
|
||||
# Miscellaneous syscalls used during process startup, at least on x86_64
|
||||
ALLOWED = DEFAULT_SET | BASIC_IO_SET | FILESYSTEM_SET | set('''
|
||||
arch_prctl
|
||||
ioctl
|
||||
madvise
|
||||
mprotect
|
||||
mremap
|
||||
prctl
|
||||
readdir
|
||||
umask
|
||||
'''.split())
|
||||
|
||||
# Syscalls we will try to use, expecting them to be either allowed or
|
||||
# blocked by our allow and/or deny lists
|
||||
TRY_SYSCALLS = [
|
||||
'chmod',
|
||||
'chroot',
|
||||
'clone3',
|
||||
'ioctl TIOCNOTTY',
|
||||
'ioctl TIOCSTI CVE-2019-10063',
|
||||
'ioctl TIOCSTI',
|
||||
'listen',
|
||||
'prctl',
|
||||
]
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
if 'G_TEST_SRCDIR' in os.environ:
|
||||
self.test_srcdir = os.getenv('G_TEST_SRCDIR') + '/tests'
|
||||
else:
|
||||
self.test_srcdir = here
|
||||
|
||||
if 'G_TEST_BUILDDIR' in os.environ:
|
||||
self.test_builddir = os.getenv('G_TEST_BUILDDIR') + '/tests'
|
||||
else:
|
||||
self.test_builddir = here
|
||||
|
||||
self.bwrap = os.getenv('BWRAP', 'bwrap')
|
||||
self.try_syscall = os.path.join(self.test_builddir, 'try-syscall')
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'true',
|
||||
],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=2,
|
||||
)
|
||||
|
||||
if completed.returncode != 0:
|
||||
raise unittest.SkipTest(
|
||||
'cannot run bwrap (does it need to be setuid?)'
|
||||
)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
pass
|
||||
|
||||
def test_no_seccomp(self) -> None:
|
||||
for syscall in TRY_SYSCALLS:
|
||||
print('# {} without seccomp'.format(syscall))
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
self.try_syscall, syscall,
|
||||
],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=2,
|
||||
)
|
||||
|
||||
if (
|
||||
syscall == 'ioctl TIOCSTI CVE-2019-10063'
|
||||
and completed.returncode == errno.ENOENT
|
||||
):
|
||||
print('# Cannot test 64-bit syscall parameter on 32-bit')
|
||||
continue
|
||||
|
||||
if syscall == 'clone3':
|
||||
# If the kernel supports it, we didn't block it so
|
||||
# it fails with EFAULT. If the kernel doesn't support it,
|
||||
# it'll fail with ENOSYS instead.
|
||||
self.assertIn(
|
||||
completed.returncode,
|
||||
(errno.ENOSYS, errno.EFAULT),
|
||||
)
|
||||
elif syscall.startswith('ioctl') or syscall == 'listen':
|
||||
self.assertEqual(completed.returncode, errno.EBADF)
|
||||
else:
|
||||
self.assertEqual(completed.returncode, errno.EFAULT)
|
||||
|
||||
def test_seccomp_allowlist(self) -> None:
|
||||
with tempfile.TemporaryFile() as allowlist_temp:
|
||||
allowlist = seccomp.SyscallFilter(seccomp.ERRNO(errno.ENOSYS))
|
||||
|
||||
if os.uname().machine == 'x86_64':
|
||||
# Allow Python and try-syscall to be different word sizes
|
||||
allowlist.add_arch(seccomp.Arch.X86)
|
||||
|
||||
for syscall in ALLOWED:
|
||||
try:
|
||||
allowlist.add_rule(seccomp.ALLOW, syscall)
|
||||
except Exception as e:
|
||||
print('# Cannot add {} to allowlist: {!r}'.format(syscall, e))
|
||||
|
||||
allowlist.export_bpf(allowlist_temp)
|
||||
|
||||
for syscall in TRY_SYSCALLS:
|
||||
print('# allowlist vs. {}'.format(syscall))
|
||||
allowlist_temp.seek(0, os.SEEK_SET)
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--seccomp', str(allowlist_temp.fileno()),
|
||||
self.try_syscall, syscall,
|
||||
],
|
||||
pass_fds=(allowlist_temp.fileno(),),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=2,
|
||||
)
|
||||
|
||||
if (
|
||||
syscall == 'ioctl TIOCSTI CVE-2019-10063'
|
||||
and completed.returncode == errno.ENOENT
|
||||
):
|
||||
print('# Cannot test 64-bit syscall parameter on 32-bit')
|
||||
continue
|
||||
|
||||
if syscall.startswith('ioctl'):
|
||||
# We allow this, so it is executed (and in this simple
|
||||
# example, immediately fails)
|
||||
self.assertEqual(completed.returncode, errno.EBADF)
|
||||
elif syscall in ('chroot', 'listen', 'clone3'):
|
||||
# We don't allow these, so they fail with ENOSYS.
|
||||
# clone3 might also be failing with ENOSYS because
|
||||
# the kernel genuinely doesn't support it.
|
||||
self.assertEqual(completed.returncode, errno.ENOSYS)
|
||||
else:
|
||||
# We allow this, so it is executed (and in this simple
|
||||
# example, immediately fails)
|
||||
self.assertEqual(completed.returncode, errno.EFAULT)
|
||||
|
||||
def test_seccomp_denylist(self) -> None:
|
||||
with tempfile.TemporaryFile() as denylist_temp:
|
||||
denylist = seccomp.SyscallFilter(seccomp.ALLOW)
|
||||
|
||||
if os.uname().machine == 'x86_64':
|
||||
# Allow Python and try-syscall to be different word sizes
|
||||
denylist.add_arch(seccomp.Arch.X86)
|
||||
|
||||
# Using ECONNREFUSED here because it's unlikely that any of
|
||||
# these syscalls will legitimately fail with that code, so
|
||||
# if they fail like this, it will be as a result of seccomp.
|
||||
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chmod')
|
||||
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chroot')
|
||||
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'prctl')
|
||||
denylist.add_rule(
|
||||
seccomp.ERRNO(errno.ECONNREFUSED), 'ioctl',
|
||||
seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI),
|
||||
)
|
||||
|
||||
denylist.export_bpf(denylist_temp)
|
||||
|
||||
for syscall in TRY_SYSCALLS:
|
||||
print('# denylist vs. {}'.format(syscall))
|
||||
denylist_temp.seek(0, os.SEEK_SET)
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--seccomp', str(denylist_temp.fileno()),
|
||||
self.try_syscall, syscall,
|
||||
],
|
||||
pass_fds=(denylist_temp.fileno(),),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=2,
|
||||
)
|
||||
|
||||
if (
|
||||
syscall == 'ioctl TIOCSTI CVE-2019-10063'
|
||||
and completed.returncode == errno.ENOENT
|
||||
):
|
||||
print('# Cannot test 64-bit syscall parameter on 32-bit')
|
||||
continue
|
||||
|
||||
if syscall == 'clone3':
|
||||
# If the kernel supports it, we didn't block it so
|
||||
# it fails with EFAULT. If the kernel doesn't support it,
|
||||
# it'll fail with ENOSYS instead.
|
||||
self.assertIn(
|
||||
completed.returncode,
|
||||
(errno.ENOSYS, errno.EFAULT),
|
||||
)
|
||||
elif syscall in ('ioctl TIOCNOTTY', 'listen'):
|
||||
# Not on the denylist
|
||||
self.assertEqual(completed.returncode, errno.EBADF)
|
||||
else:
|
||||
# We blocked all of these
|
||||
self.assertEqual(completed.returncode, errno.ECONNREFUSED)
|
||||
|
||||
def test_seccomp_stacked(self, allowlist_first=False) -> None:
|
||||
with tempfile.TemporaryFile(
|
||||
) as allowlist_temp, tempfile.TemporaryFile(
|
||||
) as denylist_temp:
|
||||
# This filter is a simplified version of what Flatpak wants
|
||||
|
||||
allowlist = seccomp.SyscallFilter(seccomp.ERRNO(errno.ENOSYS))
|
||||
denylist = seccomp.SyscallFilter(seccomp.ALLOW)
|
||||
|
||||
if os.uname().machine == 'x86_64':
|
||||
# Allow Python and try-syscall to be different word sizes
|
||||
allowlist.add_arch(seccomp.Arch.X86)
|
||||
denylist.add_arch(seccomp.Arch.X86)
|
||||
|
||||
for syscall in ALLOWED:
|
||||
try:
|
||||
allowlist.add_rule(seccomp.ALLOW, syscall)
|
||||
except Exception as e:
|
||||
print('# Cannot add {} to allowlist: {!r}'.format(syscall, e))
|
||||
|
||||
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chmod')
|
||||
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chroot')
|
||||
denylist.add_rule(
|
||||
seccomp.ERRNO(errno.ECONNREFUSED), 'ioctl',
|
||||
seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI),
|
||||
)
|
||||
|
||||
# All seccomp programs except the last must allow prctl(),
|
||||
# because otherwise we wouldn't be able to add the remaining
|
||||
# seccomp programs. We document that the last program can
|
||||
# block prctl, so test that.
|
||||
if allowlist_first:
|
||||
denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'prctl')
|
||||
|
||||
allowlist.export_bpf(allowlist_temp)
|
||||
denylist.export_bpf(denylist_temp)
|
||||
|
||||
for syscall in TRY_SYSCALLS:
|
||||
print('# stacked vs. {}'.format(syscall))
|
||||
allowlist_temp.seek(0, os.SEEK_SET)
|
||||
denylist_temp.seek(0, os.SEEK_SET)
|
||||
|
||||
if allowlist_first:
|
||||
fds = [allowlist_temp.fileno(), denylist_temp.fileno()]
|
||||
else:
|
||||
fds = [denylist_temp.fileno(), allowlist_temp.fileno()]
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--add-seccomp-fd', str(fds[0]),
|
||||
'--add-seccomp-fd', str(fds[1]),
|
||||
self.try_syscall, syscall,
|
||||
],
|
||||
pass_fds=fds,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=2,
|
||||
)
|
||||
|
||||
if (
|
||||
syscall == 'ioctl TIOCSTI CVE-2019-10063'
|
||||
and completed.returncode == errno.ENOENT
|
||||
):
|
||||
print('# Cannot test 64-bit syscall parameter on 32-bit')
|
||||
continue
|
||||
|
||||
if syscall == 'ioctl TIOCNOTTY':
|
||||
# Not denied by the denylist, and allowed by the allowlist
|
||||
self.assertEqual(completed.returncode, errno.EBADF)
|
||||
elif syscall in ('clone3', 'listen'):
|
||||
# We didn't deny these, so the denylist has no effect
|
||||
# and we fall back to the allowlist, which doesn't
|
||||
# include them either.
|
||||
# clone3 might also be failing with ENOSYS because
|
||||
# the kernel genuinely doesn't support it.
|
||||
self.assertEqual(completed.returncode, errno.ENOSYS)
|
||||
elif syscall == 'chroot':
|
||||
# This is denied by the denylist *and* not allowed by
|
||||
# the allowlist. The result depends which one we added
|
||||
# first: the most-recently-added filter "wins".
|
||||
if allowlist_first:
|
||||
self.assertEqual(
|
||||
completed.returncode,
|
||||
errno.ECONNREFUSED,
|
||||
)
|
||||
else:
|
||||
self.assertEqual(completed.returncode, errno.ENOSYS)
|
||||
elif syscall == 'prctl':
|
||||
# We can only put this on the denylist if the denylist
|
||||
# is the last to be added.
|
||||
if allowlist_first:
|
||||
self.assertEqual(
|
||||
completed.returncode,
|
||||
errno.ECONNREFUSED,
|
||||
)
|
||||
else:
|
||||
self.assertEqual(completed.returncode, errno.EFAULT)
|
||||
else:
|
||||
# chmod is allowed by the allowlist but blocked by the
|
||||
# denylist. Denying takes precedence over allowing,
|
||||
# regardless of order.
|
||||
self.assertEqual(completed.returncode, errno.ECONNREFUSED)
|
||||
|
||||
def test_seccomp_stacked_allowlist_first(self) -> None:
|
||||
self.test_seccomp_stacked(allowlist_first=True)
|
||||
|
||||
def test_seccomp_invalid(self) -> None:
|
||||
with tempfile.TemporaryFile(
|
||||
) as allowlist_temp, tempfile.TemporaryFile(
|
||||
) as denylist_temp:
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--add-seccomp-fd', '-1',
|
||||
'true',
|
||||
],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
self.assertIn(b'bwrap: Invalid fd: -1\n', completed.stderr)
|
||||
self.assertEqual(completed.returncode, 1)
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--seccomp', '0a',
|
||||
'true',
|
||||
],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
self.assertIn(b'bwrap: Invalid fd: 0a\n', completed.stderr)
|
||||
self.assertEqual(completed.returncode, 1)
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--add-seccomp-fd', str(denylist_temp.fileno()),
|
||||
'--seccomp', str(allowlist_temp.fileno()),
|
||||
'true',
|
||||
],
|
||||
pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
self.assertIn(
|
||||
b'bwrap: --seccomp cannot be combined with --add-seccomp-fd\n',
|
||||
completed.stderr,
|
||||
)
|
||||
self.assertEqual(completed.returncode, 1)
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--seccomp', str(allowlist_temp.fileno()),
|
||||
'--add-seccomp-fd', str(denylist_temp.fileno()),
|
||||
'true',
|
||||
],
|
||||
pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
self.assertIn(
|
||||
b'--add-seccomp-fd cannot be combined with --seccomp',
|
||||
completed.stderr,
|
||||
)
|
||||
self.assertEqual(completed.returncode, 1)
|
||||
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--add-seccomp-fd', str(allowlist_temp.fileno()),
|
||||
'--add-seccomp-fd', str(allowlist_temp.fileno()),
|
||||
'true',
|
||||
],
|
||||
pass_fds=(allowlist_temp.fileno(), allowlist_temp.fileno()),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
self.assertIn(
|
||||
b"bwrap: Can't read seccomp data: ",
|
||||
completed.stderr,
|
||||
)
|
||||
self.assertEqual(completed.returncode, 1)
|
||||
|
||||
allowlist_temp.write(b'\x01')
|
||||
allowlist_temp.seek(0, os.SEEK_SET)
|
||||
completed = subprocess.run(
|
||||
[
|
||||
self.bwrap,
|
||||
'--ro-bind', '/', '/',
|
||||
'--add-seccomp-fd', str(denylist_temp.fileno()),
|
||||
'--add-seccomp-fd', str(allowlist_temp.fileno()),
|
||||
'true',
|
||||
],
|
||||
pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
self.assertIn(
|
||||
b'bwrap: Invalid seccomp data, must be multiple of 8\n',
|
||||
completed.stderr,
|
||||
)
|
||||
self.assertEqual(completed.returncode, 1)
|
||||
|
||||
|
||||
def main():
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
try:
|
||||
from tap.runner import TAPTestRunner
|
||||
except ImportError:
|
||||
TAPTestRunner = None # type: ignore
|
||||
|
||||
if TAPTestRunner is not None:
|
||||
runner = TAPTestRunner()
|
||||
runner.set_stream(True)
|
||||
unittest.main(testRunner=runner)
|
||||
else:
|
||||
print('# tap.runner not available, using simple TAP output')
|
||||
print('1..1')
|
||||
program = unittest.main(exit=False)
|
||||
if program.result.wasSuccessful():
|
||||
print('ok 1 - %r' % program.result)
|
||||
else:
|
||||
print('not ok 1 - %r' % program.result)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
28
codex-rs/vendor/bubblewrap/tests/test-specifying-pidns.sh
vendored
Executable file
28
codex-rs/vendor/bubblewrap/tests/test-specifying-pidns.sh
vendored
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeuo pipefail
|
||||
|
||||
srcd=$(cd $(dirname "$0") && pwd)
|
||||
. "${srcd}/libtest.sh"
|
||||
|
||||
echo "1..1"
|
||||
|
||||
# This test needs user namespaces
|
||||
if test -n "${bwrap_is_suid:-}"; then
|
||||
echo "ok - # SKIP no setuid support for --unshare-user"
|
||||
else
|
||||
mkfifo donepipe
|
||||
$RUN --info-fd 42 --unshare-user --unshare-pid sh -c 'readlink /proc/self/ns/pid > sandbox-pidns; cat < donepipe' >/dev/null 42>info.json &
|
||||
while ! test -f sandbox-pidns; do sleep 1; done
|
||||
SANDBOX1PID=$(extract_child_pid info.json)
|
||||
|
||||
ASAN_OPTIONS=detect_leaks=0 LSAN_OPTIONS=detect_leaks=0 \
|
||||
$RUN --userns 11 --pidns 12 readlink /proc/self/ns/pid > sandbox2-pidns 11< /proc/$SANDBOX1PID/ns/user 12< /proc/$SANDBOX1PID/ns/pid
|
||||
echo foo > donepipe
|
||||
|
||||
assert_files_equal sandbox-pidns sandbox2-pidns
|
||||
|
||||
rm donepipe info.json sandbox-pidns
|
||||
|
||||
echo "ok - Test --pidns"
|
||||
fi
|
||||
28
codex-rs/vendor/bubblewrap/tests/test-specifying-userns.sh
vendored
Executable file
28
codex-rs/vendor/bubblewrap/tests/test-specifying-userns.sh
vendored
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -xeuo pipefail
|
||||
|
||||
srcd=$(cd $(dirname "$0") && pwd)
|
||||
. "${srcd}/libtest.sh"
|
||||
|
||||
echo "1..1"
|
||||
|
||||
# This test needs user namespaces
|
||||
if test -n "${bwrap_is_suid:-}"; then
|
||||
echo "ok - # SKIP no setuid support for --unshare-user"
|
||||
else
|
||||
mkfifo donepipe
|
||||
|
||||
$RUN --info-fd 42 --unshare-user sh -c 'readlink /proc/self/ns/user > sandbox-userns; cat < donepipe' >/dev/null 42>info.json &
|
||||
while ! test -f sandbox-userns; do sleep 1; done
|
||||
SANDBOX1PID=$(extract_child_pid info.json)
|
||||
|
||||
$RUN --userns 11 readlink /proc/self/ns/user > sandbox2-userns 11< /proc/$SANDBOX1PID/ns/user
|
||||
echo foo > donepipe
|
||||
|
||||
assert_files_equal sandbox-userns sandbox2-userns
|
||||
|
||||
rm donepipe info.json sandbox-userns
|
||||
|
||||
echo "ok - Test --userns"
|
||||
fi
|
||||
247
codex-rs/vendor/bubblewrap/tests/test-utils.c
vendored
Normal file
247
codex-rs/vendor/bubblewrap/tests/test-utils.c
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright © 2019-2021 Collabora Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
/* A small implementation of TAP */
|
||||
static unsigned int test_number = 0;
|
||||
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
static void
|
||||
ok (const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
printf ("ok %u - ", ++test_number);
|
||||
va_start (ap, format);
|
||||
vprintf (format, ap);
|
||||
va_end (ap);
|
||||
printf ("\n");
|
||||
}
|
||||
|
||||
/* for simplicity we always die immediately on failure */
|
||||
#define not_ok(fmt, ...) die (fmt, ## __VA_ARGS__)
|
||||
|
||||
/* approximately GLib-compatible helper macros */
|
||||
#define g_test_message(fmt, ...) printf ("# " fmt "\n", ## __VA_ARGS__)
|
||||
#define g_assert_cmpstr(left_expr, op, right_expr) \
|
||||
do { \
|
||||
const char *left = (left_expr); \
|
||||
const char *right = (right_expr); \
|
||||
if (strcmp0 (left, right) op 0) \
|
||||
ok ("%s (\"%s\") %s %s (\"%s\")", #left_expr, left, #op, #right_expr, right); \
|
||||
else \
|
||||
not_ok ("expected %s (\"%s\") %s %s (\"%s\")", \
|
||||
#left_expr, left, #op, #right_expr, right); \
|
||||
} while (0)
|
||||
#define g_assert_cmpint(left_expr, op, right_expr) \
|
||||
do { \
|
||||
intmax_t left = (left_expr); \
|
||||
intmax_t right = (right_expr); \
|
||||
if (left op right) \
|
||||
ok ("%s (%ji) %s %s (%ji)", #left_expr, left, #op, #right_expr, right); \
|
||||
else \
|
||||
not_ok ("expected %s (%ji) %s %s (%ji)", \
|
||||
#left_expr, left, #op, #right_expr, right); \
|
||||
} while (0)
|
||||
#define g_assert_cmpuint(left_expr, op, right_expr) \
|
||||
do { \
|
||||
uintmax_t left = (left_expr); \
|
||||
uintmax_t right = (right_expr); \
|
||||
if (left op right) \
|
||||
ok ("%s (%ju) %s %s (%ju)", #left_expr, left, #op, #right_expr, right); \
|
||||
else \
|
||||
not_ok ("expected %s (%ju) %s %s (%ju)", \
|
||||
#left_expr, left, #op, #right_expr, right); \
|
||||
} while (0)
|
||||
#define g_assert_true(expr) \
|
||||
do { \
|
||||
if ((expr)) \
|
||||
ok ("%s", #expr); \
|
||||
else \
|
||||
not_ok ("expected %s to be true", #expr); \
|
||||
} while (0)
|
||||
#define g_assert_false(expr) \
|
||||
do { \
|
||||
if (!(expr)) \
|
||||
ok ("!(%s)", #expr); \
|
||||
else \
|
||||
not_ok ("expected %s to be false", #expr); \
|
||||
} while (0)
|
||||
#define g_assert_null(expr) \
|
||||
do { \
|
||||
if ((expr) == NULL) \
|
||||
ok ("%s was null", #expr); \
|
||||
else \
|
||||
not_ok ("expected %s to be null", #expr); \
|
||||
} while (0)
|
||||
#define g_assert_nonnull(expr) \
|
||||
do { \
|
||||
if ((expr) != NULL) \
|
||||
ok ("%s wasn't null", #expr); \
|
||||
else \
|
||||
not_ok ("expected %s to be non-null", #expr); \
|
||||
} while (0)
|
||||
|
||||
static int
|
||||
strcmp0 (const char *left,
|
||||
const char *right)
|
||||
{
|
||||
if (left == right)
|
||||
return 0;
|
||||
|
||||
if (left == NULL)
|
||||
return -1;
|
||||
|
||||
if (right == NULL)
|
||||
return 1;
|
||||
|
||||
return strcmp (left, right);
|
||||
}
|
||||
|
||||
static void
|
||||
test_n_elements (void)
|
||||
{
|
||||
int three[] = { 1, 2, 3 };
|
||||
g_assert_cmpuint (N_ELEMENTS (three), ==, 3);
|
||||
}
|
||||
|
||||
static void
|
||||
test_strconcat (void)
|
||||
{
|
||||
const char *a = "aaa";
|
||||
const char *b = "bbb";
|
||||
char *ab = strconcat (a, b);
|
||||
g_assert_cmpstr (ab, ==, "aaabbb");
|
||||
free (ab);
|
||||
}
|
||||
|
||||
static void
|
||||
test_strconcat3 (void)
|
||||
{
|
||||
const char *a = "aaa";
|
||||
const char *b = "bbb";
|
||||
const char *c = "ccc";
|
||||
char *abc = strconcat3 (a, b, c);
|
||||
g_assert_cmpstr (abc, ==, "aaabbbccc");
|
||||
free (abc);
|
||||
}
|
||||
|
||||
static void
|
||||
test_has_prefix (void)
|
||||
{
|
||||
g_assert_true (has_prefix ("foo", "foo"));
|
||||
g_assert_true (has_prefix ("foobar", "foo"));
|
||||
g_assert_false (has_prefix ("foobar", "fool"));
|
||||
g_assert_false (has_prefix ("foo", "fool"));
|
||||
g_assert_true (has_prefix ("foo", ""));
|
||||
g_assert_true (has_prefix ("", ""));
|
||||
g_assert_false (has_prefix ("", "no"));
|
||||
g_assert_false (has_prefix ("yes", "no"));
|
||||
}
|
||||
|
||||
static void
|
||||
test_has_path_prefix (void)
|
||||
{
|
||||
static const struct
|
||||
{
|
||||
const char *str;
|
||||
const char *prefix;
|
||||
bool expected;
|
||||
} tests[] =
|
||||
{
|
||||
{ "/run/host/usr", "/run/host", true },
|
||||
{ "/run/host/usr", "/run/host/", true },
|
||||
{ "/run/host", "/run/host", true },
|
||||
{ "////run///host////usr", "//run//host", true },
|
||||
{ "////run///host////usr", "//run//host////", true },
|
||||
{ "/run/hostage", "/run/host", false },
|
||||
/* Any number of leading slashes is ignored, even zero */
|
||||
{ "foo/bar", "/foo", true },
|
||||
{ "/foo/bar", "foo", true },
|
||||
};
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < N_ELEMENTS (tests); i++)
|
||||
{
|
||||
const char *str = tests[i].str;
|
||||
const char *prefix = tests[i].prefix;
|
||||
bool expected = tests[i].expected;
|
||||
|
||||
if (expected)
|
||||
g_test_message ("%s should have path prefix %s", str, prefix);
|
||||
else
|
||||
g_test_message ("%s should not have path prefix %s", str, prefix);
|
||||
|
||||
if (expected)
|
||||
g_assert_true (has_path_prefix (str, prefix));
|
||||
else
|
||||
g_assert_false (has_path_prefix (str, prefix));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_string_builder (void)
|
||||
{
|
||||
StringBuilder sb = {0};
|
||||
|
||||
strappend (&sb, "aaa");
|
||||
g_assert_cmpstr (sb.str, ==, "aaa");
|
||||
strappend (&sb, "bbb");
|
||||
g_assert_cmpstr (sb.str, ==, "aaabbb");
|
||||
strappendf (&sb, "c%dc%s", 9, "x");
|
||||
g_assert_cmpstr (sb.str, ==, "aaabbbc9cx");
|
||||
strappend_escape_for_mount_options (&sb, "/path :,\\");
|
||||
g_assert_cmpstr (sb.str, ==, "aaabbbc9cx/path \\:\\,\\\\");
|
||||
strappend (&sb, "zzz");
|
||||
g_assert_cmpstr (sb.str, ==, "aaabbbc9cx/path \\:\\,\\\\zzz");
|
||||
|
||||
free (sb.str);
|
||||
sb = (StringBuilder){0};
|
||||
|
||||
strappend_escape_for_mount_options (&sb, "aaa");
|
||||
g_assert_cmpstr (sb.str, ==, "aaa");
|
||||
|
||||
free (sb.str);
|
||||
sb = (StringBuilder){0};
|
||||
|
||||
strappend_escape_for_mount_options (&sb, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
g_assert_cmpstr (sb.str, ==, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
|
||||
free (sb.str);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc UNUSED,
|
||||
char **argv UNUSED)
|
||||
{
|
||||
setvbuf (stdout, NULL, _IONBF, 0);
|
||||
test_n_elements ();
|
||||
test_strconcat ();
|
||||
test_strconcat3 ();
|
||||
test_has_prefix ();
|
||||
test_has_path_prefix ();
|
||||
test_string_builder ();
|
||||
printf ("1..%u\n", test_number);
|
||||
return 0;
|
||||
}
|
||||
180
codex-rs/vendor/bubblewrap/tests/try-syscall.c
vendored
Normal file
180
codex-rs/vendor/bubblewrap/tests/try-syscall.c
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 2021 Simon McVittie
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*
|
||||
* Try one or more system calls that might have been blocked by a
|
||||
* seccomp filter. Return the last value of errno seen.
|
||||
*
|
||||
* In general, we pass a bad fd or pointer to each syscall that will
|
||||
* accept one, so that it will fail with EBADF or EFAULT without side-effects.
|
||||
*
|
||||
* This helper is used for regression tests in both bubblewrap and flatpak.
|
||||
* Please keep both copies in sync.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#if defined(_MIPS_SIM)
|
||||
# if _MIPS_SIM == _ABIO32
|
||||
# define MISSING_SYSCALL_BASE 4000
|
||||
# elif _MIPS_SIM == _ABI64
|
||||
# define MISSING_SYSCALL_BASE 5000
|
||||
# elif _MIPS_SIM == _ABIN32
|
||||
# define MISSING_SYSCALL_BASE 6000
|
||||
# else
|
||||
# error "Unknown MIPS ABI"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(__ia64__)
|
||||
# define MISSING_SYSCALL_BASE 1024
|
||||
#endif
|
||||
|
||||
#if defined(__alpha__)
|
||||
# define MISSING_SYSCALL_BASE 110
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) && defined(__ILP32__)
|
||||
# define MISSING_SYSCALL_BASE 0x40000000
|
||||
#endif
|
||||
|
||||
/*
|
||||
* MISSING_SYSCALL_BASE:
|
||||
*
|
||||
* Number to add to the syscall numbers of recently-added syscalls
|
||||
* to get the appropriate syscall for the current ABI.
|
||||
*/
|
||||
#ifndef MISSING_SYSCALL_BASE
|
||||
# define MISSING_SYSCALL_BASE 0
|
||||
#endif
|
||||
|
||||
#ifndef __NR_clone3
|
||||
# define __NR_clone3 (MISSING_SYSCALL_BASE + 435)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The size of clone3's parameter (as of 2021)
|
||||
*/
|
||||
#define SIZEOF_STRUCT_CLONE_ARGS ((size_t) 88)
|
||||
|
||||
/*
|
||||
* An invalid pointer that will cause syscalls to fail with EFAULT
|
||||
*/
|
||||
#define WRONG_POINTER ((char *) 1)
|
||||
|
||||
#ifndef PR_GET_CHILD_SUBREAPER
|
||||
#define PR_GET_CHILD_SUBREAPER 37
|
||||
#endif
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
int errsv = 0;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < argc; i++)
|
||||
{
|
||||
const char *arg = argv[i];
|
||||
|
||||
if (strcmp (arg, "print-errno-values") == 0)
|
||||
{
|
||||
printf ("EBADF=%d\n", EBADF);
|
||||
printf ("EFAULT=%d\n", EFAULT);
|
||||
printf ("ENOENT=%d\n", ENOENT);
|
||||
printf ("ENOSYS=%d\n", ENOSYS);
|
||||
printf ("EPERM=%d\n", EPERM);
|
||||
}
|
||||
else if (strcmp (arg, "chmod") == 0)
|
||||
{
|
||||
/* If not blocked by seccomp, this will fail with EFAULT */
|
||||
if (chmod (WRONG_POINTER, 0700) != 0)
|
||||
{
|
||||
errsv = errno;
|
||||
perror (arg);
|
||||
}
|
||||
}
|
||||
else if (strcmp (arg, "chroot") == 0)
|
||||
{
|
||||
/* If not blocked by seccomp, this will fail with EFAULT */
|
||||
if (chroot (WRONG_POINTER) != 0)
|
||||
{
|
||||
errsv = errno;
|
||||
perror (arg);
|
||||
}
|
||||
}
|
||||
else if (strcmp (arg, "clone3") == 0)
|
||||
{
|
||||
/* If not blocked by seccomp, this will fail with EFAULT */
|
||||
if (syscall (__NR_clone3, WRONG_POINTER, SIZEOF_STRUCT_CLONE_ARGS) != 0)
|
||||
{
|
||||
errsv = errno;
|
||||
perror (arg);
|
||||
}
|
||||
}
|
||||
else if (strcmp (arg, "ioctl TIOCNOTTY") == 0)
|
||||
{
|
||||
/* If not blocked by seccomp, this will fail with EBADF */
|
||||
if (ioctl (-1, TIOCNOTTY) != 0)
|
||||
{
|
||||
errsv = errno;
|
||||
perror (arg);
|
||||
}
|
||||
}
|
||||
else if (strcmp (arg, "ioctl TIOCSTI") == 0)
|
||||
{
|
||||
/* If not blocked by seccomp, this will fail with EBADF */
|
||||
if (ioctl (-1, TIOCSTI, WRONG_POINTER) != 0)
|
||||
{
|
||||
errsv = errno;
|
||||
perror (arg);
|
||||
}
|
||||
}
|
||||
#ifdef __LP64__
|
||||
else if (strcmp (arg, "ioctl TIOCSTI CVE-2019-10063") == 0)
|
||||
{
|
||||
unsigned long not_TIOCSTI = (0x123UL << 32) | (unsigned long) TIOCSTI;
|
||||
|
||||
/* If not blocked by seccomp, this will fail with EBADF */
|
||||
if (syscall (__NR_ioctl, -1, not_TIOCSTI, WRONG_POINTER) != 0)
|
||||
{
|
||||
errsv = errno;
|
||||
perror (arg);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (strcmp (arg, "listen") == 0)
|
||||
{
|
||||
/* If not blocked by seccomp, this will fail with EBADF */
|
||||
if (listen (-1, 42) != 0)
|
||||
{
|
||||
errsv = errno;
|
||||
perror (arg);
|
||||
}
|
||||
}
|
||||
else if (strcmp (arg, "prctl") == 0)
|
||||
{
|
||||
/* If not blocked by seccomp, this will fail with EFAULT */
|
||||
if (prctl (PR_GET_CHILD_SUBREAPER, WRONG_POINTER, 0, 0, 0) != 0)
|
||||
{
|
||||
errsv = errno;
|
||||
perror (arg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "Unsupported syscall \"%s\"\n", arg);
|
||||
errsv = ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
return errsv;
|
||||
}
|
||||
2
codex-rs/vendor/bubblewrap/tests/use-as-subproject/.gitignore
vendored
Normal file
2
codex-rs/vendor/bubblewrap/tests/use-as-subproject/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/_build/
|
||||
/subprojects/
|
||||
3
codex-rs/vendor/bubblewrap/tests/use-as-subproject/README
vendored
Normal file
3
codex-rs/vendor/bubblewrap/tests/use-as-subproject/README
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
This is a simple example of a project that uses bubblewrap as a
|
||||
subproject. The intention is that if this project can successfully build
|
||||
bubblewrap as a subproject, then so could Flatpak.
|
||||
26
codex-rs/vendor/bubblewrap/tests/use-as-subproject/assert-correct-rpath.py
vendored
Executable file
26
codex-rs/vendor/bubblewrap/tests/use-as-subproject/assert-correct-rpath.py
vendored
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/python3
|
||||
# Copyright 2022 Collabora Ltd.
|
||||
# SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
if __name__ == '__main__':
|
||||
completed = subprocess.run(
|
||||
['objdump', '-T', '-x', sys.argv[1]],
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
stdout = completed.stdout
|
||||
assert stdout is not None
|
||||
seen_rpath = False
|
||||
|
||||
for line in stdout.splitlines():
|
||||
words = line.strip().split()
|
||||
|
||||
if words and words[0] in (b'RPATH', b'RUNPATH'):
|
||||
print(line.decode(errors='backslashreplace'))
|
||||
assert len(words) == 2, words
|
||||
assert words[1] == b'${ORIGIN}/../lib', words
|
||||
seen_rpath = True
|
||||
|
||||
assert seen_rpath
|
||||
1
codex-rs/vendor/bubblewrap/tests/use-as-subproject/config.h
vendored
Normal file
1
codex-rs/vendor/bubblewrap/tests/use-as-subproject/config.h
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#error Should not use superproject config.h to compile bubblewrap
|
||||
1
codex-rs/vendor/bubblewrap/tests/use-as-subproject/dummy-config.h.in
vendored
Normal file
1
codex-rs/vendor/bubblewrap/tests/use-as-subproject/dummy-config.h.in
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#error Should not use superproject generated config.h to compile bubblewrap
|
||||
20
codex-rs/vendor/bubblewrap/tests/use-as-subproject/meson.build
vendored
Normal file
20
codex-rs/vendor/bubblewrap/tests/use-as-subproject/meson.build
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
project(
|
||||
'use-bubblewrap-as-subproject',
|
||||
'c',
|
||||
version : '0',
|
||||
meson_version : '>=0.49.0',
|
||||
)
|
||||
|
||||
configure_file(
|
||||
output : 'config.h',
|
||||
input : 'dummy-config.h.in',
|
||||
configuration : configuration_data(),
|
||||
)
|
||||
|
||||
subproject(
|
||||
'bubblewrap',
|
||||
default_options : [
|
||||
'install_rpath=${ORIGIN}/../lib',
|
||||
'program_prefix=not-flatpak-',
|
||||
],
|
||||
)
|
||||
Reference in New Issue
Block a user