summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/check.yml1
-rw-r--r--bind-mount.c65
-rw-r--r--bind-mount.h9
-rw-r--r--bubblewrap.c14
-rw-r--r--meson.build2
-rw-r--r--meson_options.txt10
-rwxr-xr-xtests/test-run.sh11
-rwxr-xr-xtests/use-as-subproject/assert-correct-rpath.py26
-rw-r--r--tests/use-as-subproject/meson.build1
9 files changed, 111 insertions, 28 deletions
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index ceb3b82..d55d158 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -127,6 +127,7 @@ jobs:
test -x DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap
test ! -e DESTDIR-as-subproject/usr/local/bin/bwrap
test ! -e DESTDIR-as-subproject/usr/local/libexec/bwrap
+ tests/use-as-subproject/assert-correct-rpath.py DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap
- name: Upload test logs
uses: actions/upload-artifact@v1
if: failure() || cancelled()
diff --git a/bind-mount.c b/bind-mount.c
index 877b095..488d85a 100644
--- a/bind-mount.c
+++ b/bind-mount.c
@@ -378,7 +378,8 @@ bind_mount_result
bind_mount (int proc_fd,
const char *src,
const char *dest,
- bind_option_t options)
+ bind_option_t options,
+ char **failing_path)
{
bool readonly = (options & BIND_READONLY) != 0;
bool devices = (options & BIND_DEVICES) != 0;
@@ -406,7 +407,12 @@ bind_mount (int proc_fd,
dest_fd = open (resolved_dest, O_PATH | O_CLOEXEC);
if (dest_fd < 0)
- return BIND_MOUNT_ERROR_REOPEN_DEST;
+ {
+ if (failing_path != NULL)
+ *failing_path = steal_pointer (&resolved_dest);
+
+ return BIND_MOUNT_ERROR_REOPEN_DEST;
+ }
/* If we are in a case-insensitive filesystem, mountinfo might contain a
* different case combination of the path we requested to mount.
@@ -422,11 +428,19 @@ bind_mount (int proc_fd,
oldroot_dest_proc = get_oldroot_path (dest_proc);
kernel_case_combination = readlink_malloc (oldroot_dest_proc);
if (kernel_case_combination == NULL)
- return BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD;
+ {
+ if (failing_path != NULL)
+ *failing_path = steal_pointer (&resolved_dest);
+
+ return BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD;
+ }
mount_tab = parse_mountinfo (proc_fd, kernel_case_combination);
if (mount_tab[0].mountpoint == NULL)
{
+ if (failing_path != NULL)
+ *failing_path = steal_pointer (&kernel_case_combination);
+
errno = EINVAL;
return BIND_MOUNT_ERROR_FIND_DEST_MOUNT;
}
@@ -437,7 +451,12 @@ bind_mount (int proc_fd,
if (new_flags != current_flags &&
mount ("none", resolved_dest,
NULL, MS_SILENT | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
- return BIND_MOUNT_ERROR_REMOUNT_DEST;
+ {
+ if (failing_path != NULL)
+ *failing_path = steal_pointer (&resolved_dest);
+
+ return BIND_MOUNT_ERROR_REMOUNT_DEST;
+ }
/* We need to work around the fact that a bind mount does not apply the flags, so we need to manually
* apply the flags to all submounts in the recursive case.
@@ -456,7 +475,12 @@ bind_mount (int proc_fd,
/* If we can't read the mountpoint we can't remount it, but that should
be safe to ignore because its not something the user can access. */
if (errno != EACCES)
- return BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT;
+ {
+ if (failing_path != NULL)
+ *failing_path = xstrdup (mount_tab[i].mountpoint);
+
+ return BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT;
+ }
}
}
}
@@ -469,50 +493,53 @@ bind_mount (int proc_fd,
* If want_errno_p is non-NULL, *want_errno_p is used to indicate whether
* it would make sense to print strerror(saved_errno).
*/
-const char *
+static char *
bind_mount_result_to_string (bind_mount_result res,
+ const char *failing_path,
bool *want_errno_p)
{
- const char *string;
+ char *string = NULL;
bool want_errno = TRUE;
switch (res)
{
case BIND_MOUNT_ERROR_MOUNT:
- string = "Unable to mount source on destination";
+ string = xstrdup ("Unable to mount source on destination");
break;
case BIND_MOUNT_ERROR_REALPATH_DEST:
- string = "realpath(destination)";
+ string = xstrdup ("realpath(destination)");
break;
case BIND_MOUNT_ERROR_REOPEN_DEST:
- string = "open(destination, O_PATH)";
+ string = xasprintf ("open(\"%s\", O_PATH)", failing_path);
break;
case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD:
- string = "readlink(/proc/self/fd/<destination>)";
+ string = xasprintf ("readlink(/proc/self/fd/N) for \"%s\"", failing_path);
break;
case BIND_MOUNT_ERROR_FIND_DEST_MOUNT:
- string = "Unable to find destination in mount table";
+ string = xasprintf ("Unable to find \"%s\" in mount table", failing_path);
want_errno = FALSE;
break;
case BIND_MOUNT_ERROR_REMOUNT_DEST:
- string = "Unable to remount destination with correct flags";
+ string = xasprintf ("Unable to remount destination \"%s\" with correct flags",
+ failing_path);
break;
case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT:
- string = "Unable to remount recursively with correct flags";
+ string = xasprintf ("Unable to apply mount flags: remount \"%s\"",
+ failing_path);
break;
case BIND_MOUNT_SUCCESS:
- string = "Success";
+ string = xstrdup ("Success");
break;
default:
- string = "(unknown/invalid bind_mount_result)";
+ string = xstrdup ("(unknown/invalid bind_mount_result)");
break;
}
@@ -525,11 +552,13 @@ bind_mount_result_to_string (bind_mount_result res,
void
die_with_bind_result (bind_mount_result res,
int saved_errno,
+ const char *failing_path,
const char *format,
...)
{
va_list args;
bool want_errno = TRUE;
+ char *message;
fprintf (stderr, "bwrap: ");
@@ -537,7 +566,9 @@ die_with_bind_result (bind_mount_result res,
vfprintf (stderr, format, args);
va_end (args);
- fprintf (stderr, ": %s", bind_mount_result_to_string (res, &want_errno));
+ message = bind_mount_result_to_string (res, failing_path, &want_errno);
+ fprintf (stderr, ": %s", message);
+ /* message is leaked, but we're exiting unsuccessfully anyway, so ignore */
if (want_errno)
fprintf (stderr, ": %s", strerror (saved_errno));
diff --git a/bind-mount.h b/bind-mount.h
index 1fac3e5..8a361fb 100644
--- a/bind-mount.h
+++ b/bind-mount.h
@@ -42,14 +42,13 @@ typedef enum
bind_mount_result bind_mount (int proc_fd,
const char *src,
const char *dest,
- bind_option_t options);
-
-const char *bind_mount_result_to_string (bind_mount_result res,
- bool *want_errno);
+ bind_option_t options,
+ char **failing_path);
void die_with_bind_result (bind_mount_result res,
int saved_errno,
+ const char *failing_path,
const char *format,
...)
__attribute__((__noreturn__))
- __attribute__((format (printf, 3, 4)));
+ __attribute__((format (printf, 4, 5)));
diff --git a/bubblewrap.c b/bubblewrap.c
index 9e67eb5..f2e4a12 100644
--- a/bubblewrap.c
+++ b/bubblewrap.c
@@ -1017,6 +1017,7 @@ privileged_op (int privileged_op_socket,
const char *arg2)
{
bind_mount_result bind_result;
+ char *failing_path = NULL;
if (privileged_op_socket != -1)
{
@@ -1083,23 +1084,25 @@ privileged_op (int privileged_op_socket,
break;
case PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE:
- bind_result = bind_mount (proc_fd, NULL, arg2, BIND_READONLY);
+ bind_result = bind_mount (proc_fd, NULL, arg2, BIND_READONLY, &failing_path);
if (bind_result != BIND_MOUNT_SUCCESS)
- die_with_bind_result (bind_result, errno,
+ die_with_bind_result (bind_result, errno, failing_path,
"Can't remount readonly on %s", arg2);
+ assert (failing_path == NULL); /* otherwise we would have died */
break;
case PRIV_SEP_OP_BIND_MOUNT:
/* We always bind directories recursively, otherwise this would let us
access files that are otherwise covered on the host */
- bind_result = bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags);
+ bind_result = bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags, &failing_path);
if (bind_result != BIND_MOUNT_SUCCESS)
- die_with_bind_result (bind_result, errno,
+ die_with_bind_result (bind_result, errno, failing_path,
"Can't bind mount %s on %s", arg1, arg2);
+ assert (failing_path == NULL); /* otherwise we would have died */
break;
case PRIV_SEP_OP_PROC_MOUNT:
@@ -2854,6 +2857,9 @@ main (int argc,
die ("No permissions to creating new namespace, likely because the kernel does not allow non-privileged user namespaces. On e.g. debian this can be enabled with 'sysctl kernel.unprivileged_userns_clone=1'.");
}
+ if (errno == ENOSPC)
+ die ("Creating new namespace failed: nesting depth or /proc/sys/user/max_*_namespaces exceeded (ENOSPC)");
+
die_with_error ("Creating new namespace failed");
}
diff --git a/meson.build b/meson.build
index 404dd2e..0e1f110 100644
--- a/meson.build
+++ b/meson.build
@@ -121,8 +121,10 @@ bwrap = executable(
'network.c',
'utils.c',
],
+ build_rpath : get_option('build_rpath'),
install : true,
install_dir : bwrapdir,
+ install_rpath : get_option('install_rpath'),
dependencies : [selinux_dep, libcap_dep],
)
diff --git a/meson_options.txt b/meson_options.txt
index 87bac9c..10a0a20 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -16,6 +16,16 @@ option(
description : 'install bwrap in this directory [default: bindir, or libexecdir in subprojects]',
)
option(
+ 'build_rpath',
+ type : 'string',
+ description : 'set a RUNPATH or RPATH on the bwrap executable',
+)
+option(
+ 'install_rpath',
+ type : 'string',
+ description : 'set a RUNPATH or RPATH on the bwrap executable',
+)
+option(
'man',
type : 'feature',
description : 'generate man pages',
diff --git a/tests/test-run.sh b/tests/test-run.sh
index cafc580..3e5e9e6 100755
--- a/tests/test-run.sh
+++ b/tests/test-run.sh
@@ -39,9 +39,16 @@ for ALT in "" "--unshare-user-try" "--unshare-pid" "--unshare-user-try --unshare
CAP=""
fi
- if ! ${is_uidzero} && $RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /etc/shadow; then
+ 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
+
echo "ok - cannot read /etc/shadow with $ALT"
# Unreadable dir
if [ "x$UNREADABLE" != "x" ]; then
@@ -88,7 +95,7 @@ done
echo "ok namespace id info in info and json-status fd"
-if ! which strace >/dev/null 2>/dev/null || ! strace -h | grep -v -e default | grep -e fault >/dev/null; then
+if ! command -v strace >/dev/null || ! strace -h | grep -v -e default | grep -e fault >/dev/null; then
echo "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
diff --git a/tests/use-as-subproject/assert-correct-rpath.py b/tests/use-as-subproject/assert-correct-rpath.py
new file mode 100755
index 0000000..10b0947
--- /dev/null
+++ b/tests/use-as-subproject/assert-correct-rpath.py
@@ -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
diff --git a/tests/use-as-subproject/meson.build b/tests/use-as-subproject/meson.build
index 802fd61..bc4781c 100644
--- a/tests/use-as-subproject/meson.build
+++ b/tests/use-as-subproject/meson.build
@@ -14,6 +14,7 @@ configure_file(
subproject(
'bubblewrap',
default_options : [
+ 'install_rpath=${ORIGIN}/../lib',
'program_prefix=not-flatpak-',
],
)