diff options
-rw-r--r-- | .github/workflows/check.yml | 1 | ||||
-rw-r--r-- | bind-mount.c | 65 | ||||
-rw-r--r-- | bind-mount.h | 9 | ||||
-rw-r--r-- | bubblewrap.c | 14 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | meson_options.txt | 10 | ||||
-rwxr-xr-x | tests/test-run.sh | 11 | ||||
-rwxr-xr-x | tests/use-as-subproject/assert-correct-rpath.py | 26 | ||||
-rw-r--r-- | tests/use-as-subproject/meson.build | 1 |
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-', ], ) |