Compare commits

62 Commits

Author SHA1 Message Date
Simon McVittie
9ca3b05ec7 Prepare v0.11.0
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-30 16:07:47 +00:00
Simon McVittie
73264b77d2 Update NEWS
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-30 15:20:23 +00:00
Simon McVittie
833b32d834 Merge pull request #664 from smcv/completions
completions: Don't try to define more than one variable for pkg-config
2024-10-30 14:55:50 +00:00
Simon McVittie
4021ac2936 NEWS.md: Add items so far for 0.11.0
I find it easier to write this as I go along, instead of writing it all
as part of doing each release.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-18 17:41:13 +01:00
Simon McVittie
3e11554a0d completions: Don't try to define more than one variable for pkg-config
Before Meson 1.3.0, this would not do what we meant (instead defining
prefix to a wrong value composed from the remaining arguments).

The only reason we needed to redefine prefix in the first place is that
bash-completion older than 2.10 did not allow users of its
pkg-config file to override the datadir used to compute its
completionsdir, but that was addressed in version 2.10 (2019).
Users of older bash-completion should set bubblewrap's
bash_completion_dir build option, if the automatically-discovered
default is not appropriate.

Related to https://github.com/containers/bubblewrap/issues/609

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-18 17:21:42 +01:00
Simon McVittie
0545e72383 Merge pull request #662 from smcv/cmsg
Ensure correct alignment when dealing with control messages
2024-10-18 13:35:42 +01:00
Simon McVittie
1fd7c383ef utils: Ensure that the buffer for struct cmsghdr is suitably-aligned
A char array on the stack is not guaranteed to have any particular
alignment.

Resolves: https://github.com/containers/bubblewrap/issues/637
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-17 16:46:53 +01:00
Simon McVittie
33923454fd utils: Don't assume cmsg data is aligned suitably for struct ucred
As documented in cmsg(3), the alignment of control messages is not
guaranteed, so for portability to architectures with strong alignment
requirements we should memcpy to and from a suitably aligned instance
of the desired data structure on the stack.

Helps: https://github.com/containers/bubblewrap/issues/637
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-17 16:46:53 +01:00
Simon McVittie
759517f892 Merge pull request #663 from smcv/builtin-overflow
utils: Add a fallback version of xadd, xmul for ancient gcc
2024-10-17 16:46:47 +01:00
Simon McVittie
bcd9614380 utils: Add a fallback version of xadd, xmul for ancient gcc
bubblewrap is used in some surprisingly old environments, including
version 1 of the Steam Runtime, which has gcc 4.6 or 4.8 as its
default compiler (depending on exactly how you define "default").
These very old versions don't support the builtin used here.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-16 17:53:40 +01:00
Simon McVittie
c1bfc72043 utils: Add doc-comments for xadd, xmul
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-16 17:28:16 +01:00
Simon McVittie
ff33964f33 Merge pull request #547 from rhendric/rhendric/overlayfs
Add --overlay and related options
2024-10-16 17:27:17 +01:00
Ryan Hendrickson
f371022ad6 Add --overlay and related options
This commit adds --overlay, --tmp-overlay, --ro-overlay, and
--overlay-src options to enable bubblewrap to create overlay mounts.
These options are only permitted when bubblewrap is not installed
setuid.

Resolves: https://github.com/containers/bubblewrap/issues/412
Co-authored-by: William Manley <will@williammanley.net>
Signed-off-by: Ryan Hendrickson <ryan.hendrickson@alum.mit.edu>
[smcv: Fix merge conflicts with #660]
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-15 14:22:56 -04:00
Simon McVittie
50cdea6788 Merge pull request #660 from smcv/stdbool
Use stdbool.h for booleans

Reviewed-by: swick
2024-10-08 13:38:07 +01:00
Simon McVittie
812cc7f621 Merge pull request #661 from smcv/Wshadow
utils: Avoid shadowing the names of global functions

Reviewed-by: swick
2024-10-08 13:37:27 +01:00
Simon McVittie
47ff41eb90 Use stdbool.h for booleans
We don't need to reinvent these, especially in a confusing form
(bool type like stdbool.h, but TRUE and FALSE constants like GLib).
stdbool.h was available in the gcc 4.6 that is the default compiler in
Ubuntu 12.04, more than a decade ago, so it seems sufficiently
ubiquitous.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-08 13:35:20 +01:00
Simon McVittie
73abd50683 utils: Avoid shadowing the names of global functions
dirfd() and socket() are POSIX standard library functions, so use dfd
and sockfd instead.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-08 13:34:18 +01:00
Simon McVittie
5dab8b8916 Merge pull request #659 from smcv/issue657
Handle EINTR on blocking I/O

Reviewed-by: swick
2024-10-08 13:32:25 +01:00
Simon McVittie
0c9646573f Handle EINTR when doing I/O on files or sockets
If a blocking operation is interrupted by a signal, including SIGCHLD,
various things can fail with EINTR. This is not a "real" error and can
result in spurious failures.

Resolves: https://github.com/containers/bubblewrap/issues/657
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-03 17:55:37 +01:00
Simon McVittie
654a25d408 utils: Move TEMP_FAILURE_RETRY reimplementation here
This will allow it to be used in more places.

Helps: https://github.com/containers/bubblewrap/issues/657
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-10-03 17:55:37 +01:00
Simon McVittie
2a552429ec Merge pull request #566 from TotalCaesar659/patch-1
Update URLs to HTTPS
2024-10-01 19:37:33 +01:00
Simon McVittie
2cca54f7c6 Merge pull request #646 from smcv/wip/smcv/level-prefix
Add new --level-prefix option
2024-09-30 20:02:47 +01:00
Simon McVittie
0f99752f6c Merge pull request #647 from sertonix/bool-options
meson: use boolean value for boolean options
2024-09-30 20:02:27 +01:00
Simon McVittie
2d382867c9 Merge pull request #627 from sertonix/stat--format
tests: use stat -c instead of --format for compatibility
2024-09-30 20:01:06 +01:00
Sertonix
c17b4a8709 tests: use stat -c instead of --format for compatibility
eg. busybox stat only has -c and not --format

Signed-off-by: Sertonix <sertonix@posteo.net>
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-09-30 19:54:48 +01:00
Simon McVittie
67400e1af6 Merge pull request #658 from smcv/actions
Update Github Actions configuration
2024-09-30 19:54:39 +01:00
Simon McVittie
fc1dd3dd37 workflows: Use latest version of actions/checkout
It isn't entirely clear to me what the incompatibilities are, but
hopefully in simple cases like ours it's functionally equivalent.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-09-30 19:49:08 +01:00
Simon McVittie
11b81bbf10 workflows: Use upload-artifact@v4
Reference: https://github.blog/changelog/2024-02-13-deprecation-notice-v1-and-v2-of-the-artifact-actions/
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-09-30 19:46:32 +01:00
Simon McVittie
d2f85e51a4 Merge pull request #625 from WhyNotHugo/drop-autotools
Remove autotools build system
2024-09-03 14:00:12 +01:00
Hugo Osvaldo Barrera
2834c01cab Remove autotools build system
Signed-off-by: Hugo Osvaldo Barrera <hugo@whynothugo.nl>
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-09-03 13:47:25 +01:00
Simon McVittie
89ae6b10a4 Add new --level-prefix option
This prepends a severity level such as <3> to each line of diagnostic
output, with numeric severity levels taken from matching syslog(3)
(such as LOG_ERR = 3), so that the diagnostic output can be parsed by
tools like `logger --prio-prefix` and `systemd-cat --level-prefix=1`
that support that encoding.

The facility (LOG_USER, etc.) is not included, since it makes little
sense to vary on a per-message basis. logger(1) supports prefixes
with or without a facility, and systemd-cat(1) only supports prefixes
without a facility, so this is compatible with both.

A future version of Steam's pressure-vessel is likely to use this to
make warnings and fatal errors from bubblewrap more visible.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-08-15 15:01:35 +01:00
Simon McVittie
9b9fa15a72 utils: Put nearly all diagnostic output through a common log function
This takes a syslog-style severity level, allowing a larger program
that runs bwrap and reads a pipe from its stderr to filter or highlight
messages  based on the severity.

Take the opportunity to make the __debug__ macro (which normally expands
to nothing, but can be enabled by changing a `#if 0` to `#if 1`) less
weird and easier to use, by taking it out of the reserved-for-the-compiler
namespace, adding a newline automatically, and not requiring nested
parentheses.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-08-15 15:01:00 +01:00
Simon McVittie
2514597193 test-run: Assert that repeating --chdir logs a warning
This is the case since commit 0d369cd "main: Warn when
non-repeatable options are repeated".

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-08-15 15:00:57 +01:00
Simon McVittie
dc63ec667e Prepare v0.10.0
Signed-off-by: Simon McVittie <smcv@debian.org>
2024-08-14 09:30:44 +01:00
Simon McVittie
8b7a174930 Bump version number for a 0.10.0 release candidate
Signed-off-by: Simon McVittie <smcv@debian.org>
2024-08-12 18:58:09 +01:00
Sertonix
aeeade8ef6 meson: use boolean value for boolean options
string values for boolean options will be deprecated in a future version
of meson.

Signed-off-by: Sertonix <sertonix@posteo.net>
2024-07-25 21:52:20 +02:00
Alexander Larsson
a253257cd2 Add --bind-fd and --ro-bind-fd to let you bind a O_PATH fd.
This is useful for example if you for some reason don't have the real
path. It is also a way to make bind-mounts race-free (i.e. to have the
mount actually be the thing you wanted to be mounted, avoiding issues
where some other process replaces the target in parallel with the bwrap
launch.

Unfortunately due to some technical details we can't actually directly
mount the dirfd, as they come from different user namespace which is not
permitted, but at least we can delay resolving the fd to a path as much as
possible, and then validate after mount that we actually mounted the right
thing.

Signed-off-by: Alexander Larsson <alexl@redhat.com>
2024-07-16 18:14:03 +01:00
Simon McVittie
973fe36146 Merge pull request #636 from alexlarsson/fix-flag-typo
Fix SetupOpFlag value to be proper format
2024-06-18 12:20:35 +01:00
Alexander Larsson
83af951948 Fix SetupOpFlag value to be proper format
For some reason the second flags is "2<<0", but really flags should
be 1<<N, and in this case 1<<1. Both happen to be the same value, so its
not like this matter deeply, but lets fix it if we do later changes.

Signed-off-by: Alexander Larsson <alexl@redhat.com>
2024-06-18 10:06:03 +02:00
Simon McVittie
8e51677abd Prepare v0.9.0
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-03-26 21:08:55 +00:00
Simon McVittie
041e3c5085 Merge pull request #624 from smcv/ssize-max
utils: Don't let ssize_t overflow when reading very large files
2024-03-26 20:45:59 +00:00
Simon McVittie
b6bbba5ceb utils: Don't let ssize_t overflow when reading very large files
The size to be allocated is tracked as ssize_t, so if it's larger than
this, doubling it would cause a signed overflow.

Limiting the data we will read into memory to SSIZE_MAX/2 still lets it
occupy 25% of addressable memory (1 GiB on 32-bit or some very large
amount on 64-bit), which should be adequate. In practice we expect this
function to read a few KiB at most.

In practice we're likely to run out of memory before reaching this
point; changing this to SSIZE_MAX / 8, compiling as 32-bit and running
`${builddir}/bwrap --args 0 < /dev/zero` is a convenient way to test
this code path.

Fixes: 422c078e "Check for allocation size overflows"
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-03-25 17:52:32 +00:00
Simon McVittie
8653799bb6 release-checklist: Plan to make future releases with meson dist 2024-03-20 17:21:11 +00:00
Simon McVittie
f4c7e48870 Merge pull request #623 from smcv/codeql
workflows: Upgrade codeql-action to v2
2024-02-15 14:46:11 +00:00
Simon McVittie
7fe0996b77 Merge pull request #556 from cgzones/alloc
Allocation improvements
2024-02-15 14:45:53 +00:00
Simon McVittie
65a19f273f Merge pull request #622 from smcv/exeext
Silence Automake warning for items in TESTS being renamed
2024-02-15 14:39:11 +00:00
Simon McVittie
8789aa42e4 workflows: Upgrade codeql-action to v2
Reference: https://github.blog/changelog/2023-01-18-code-scanning-codeql-action-v1-is-now-deprecated/
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-02-15 14:38:29 +00:00
David M. Rogers
454bfbf9da Silence Automake warning for items in TESTS being renamed
The manual rule for test-bwrap in Makefile-bwrap.in threw a
warning because automake [renames the target of things in
TESTS](https://www.gnu.org/software/automake/manual/html_node/Extending.html).

Signed-off-by: David M. Rogers <predictivestatmech@gmail.com>
[smcv: Split out from a larger commit, re-worded commit message]
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-02-15 14:33:27 +00:00
Simon McVittie
82d6197e90 Merge pull request #621 from smcv/readme-sandbox-limitations
Document some potential pitfalls in sandboxing
2024-02-15 14:17:51 +00:00
Simon McVittie
526d6ebd74 Document some potential pitfalls in sandboxing
Signed-off-by: Simon Brand <simon.brand@postadigitale.de>
[smcv: Combine with #560, re-word]
Co-authored-by: Simon McVittie <smcv@collabora.com>
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-02-15 14:15:04 +00:00
Simon McVittie
623780a35b Merge pull request #619 from smcv/symlink-idempotent
Add documentation and test coverage for #577
2024-02-05 11:12:16 +00:00
Christian Göttsche
84dc66c5ee Avoid undefined behavior in realloc
Passing a NULL pointer and a size of 0 is currently implementation
defined behavior[1] and will become (with C23) undefined behavior[2].

[1]: https://manpages.debian.org/unstable/manpages-dev/realloc.3.en.html
[2]: https://en.cppreference.com/w/c/memory/realloc

Assert the passed size is always non-zero, which the current codebase
satisfies.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2024-02-02 20:46:20 +01:00
Christian Göttsche
422c078ed8 Check for allocation size overflows
Avoid integer overflows in allocation size variables to avoid passing 0
to xrealloc().

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2024-02-02 20:44:43 +01:00
Christian Göttsche
9aa1b3636b Rework xcalloc
Pass the first parameter to calloc(3) to perform the overflow check.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
2024-02-02 19:53:08 +01:00
Simon McVittie
5bc4c68006 bwrap(1): Document idempotent creation of symlinks
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-01-31 16:45:03 +00:00
Simon McVittie
913f92be36 tests: Add test coverage for #577
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-01-31 16:20:50 +00:00
Simon McVittie
51a818858c Merge pull request #577 from jkaivo/main
Make --symlink idempotent
2024-01-31 16:20:45 +00:00
Jakob Kaivo
4109d59251 Make --symlink idempotent
When creating symlinks specified with --symlink, check errno if
creation fails. If the reason was EEXIST, check the path of the
existing symlink. If it matches what was desired, continue as
normal.

Resolves: containers/bubblewrap#549
Signed-off-by: Jakob Kaivo <jkk@ung.org>
[Anders Blomdell: use readlink_malloc()]
[smcv: Squash multiple changes into one; coding style fixes]
Co-authored-by: Anders Blomdell
Co-authored-by: Simon McVittie <smcv@collabora.com>
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-01-31 16:10:07 +00:00
Simon McVittie
4872aefb1d Merge pull request #615 from smcv/mount-enospc
Show a better error message for mount(2) -> ENOSPC
2024-01-29 12:55:39 +00:00
Simon McVittie
68a0330ce4 utils: Add a specialized function to print errno for mount(2)
mount(2) uses ENOSPC to represent an arbitrary anti-denial-of-service
limit being exceeded, which is outside the usual meaning of
"No space left on device". We can make this clearer by catching that
particular failure mode and giving users a hint.

Reference: https://bugzilla.kernel.org/show_bug.cgi?id=218336
Resolves: https://github.com/ValveSoftware/steam-runtime/issues/637
Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-01-03 15:31:51 +00:00
Simon McVittie
4351ec20e8 utils: Give warnv() the ability to log strerror() or similar
This lets die_with_error() reuse it.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-01-03 15:02:57 +00:00
TotalCaesar659
4518233eac Update URLs to HTTPS
Signed-off-by: TotalCaesar659 14265316+TotalCaesar659@users.noreply.github.com
2023-03-07 23:41:49 +03:00
24 changed files with 1066 additions and 1238 deletions

View File

@@ -9,72 +9,12 @@ on:
- main
jobs:
check:
name: Build with Autotools and gcc, and test
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v1
- name: Install build-dependencies
run: sudo ./ci/builddeps.sh
- name: Create logs dir
run: mkdir test-logs
- name: autogen.sh
run: NOCONFIGURE=1 ./autogen.sh
- name: configure
run: |
mkdir _build
pushd _build
../configure \
--enable-man \
--enable-selinux \
${NULL+}
popd
env:
CFLAGS: >-
-O2
-Wp,-D_FORTIFY_SOURCE=2
-fsanitize=address
-fsanitize=undefined
- name: make
run: make -C _build -j $(getconf _NPROCESSORS_ONLN) V=1
- name: smoke-test
run: |
set -x
./_build/bwrap --bind / / --tmpfs /tmp true
env:
ASAN_OPTIONS: detect_leaks=0
- name: check
run: |
make -C _build -j $(getconf _NPROCESSORS_ONLN) check VERBOSE=1 BWRAP_MUST_WORK=1
env:
ASAN_OPTIONS: detect_leaks=0
- name: Collect overall test logs on failure
if: failure()
run: mv _build/test-suite.log test-logs/ || true
- name: Collect individual test logs on cancel
if: failure() || cancelled()
run: mv _build/tests/*.log test-logs/ || true
- name: Upload test logs
uses: actions/upload-artifact@v1
if: failure() || cancelled()
with:
name: test logs
path: test-logs
- name: install
run: |
make -C _build install DESTDIR="$(pwd)/DESTDIR"
( cd DESTDIR && find -ls )
- name: distcheck
run: |
make -C _build -j $(getconf _NPROCESSORS_ONLN) distcheck VERBOSE=1 BWRAP_MUST_WORK=1
meson:
name: Build with Meson and gcc, and test
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Install build-dependencies
run: sudo ./ci/builddeps.sh
- name: Create logs dir
@@ -129,7 +69,7 @@ jobs:
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
uses: actions/upload-artifact@v4
if: failure() || cancelled()
with:
name: test logs
@@ -145,23 +85,19 @@ jobs:
- cpp
steps:
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Check out
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Install build-dependencies
run: sudo ./ci/builddeps.sh --clang
- name: autogen.sh
run: NOCONFIGURE=1 ./autogen.sh
- name: configure
run: ./configure --enable-selinux
- run: meson build -Dselinux=enabled
env:
CC: clang
CFLAGS: >-
-O2
-Werror=unused-variable
- name: make
run: make -j $(getconf _NPROCESSORS_ONLN) V=1
- run: meson compile -C build
- name: CodeQL analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -1,12 +0,0 @@
bwrap_SOURCES = \
$(bwrap_srcpath)/bubblewrap.c \
$(bwrap_srcpath)/bind-mount.h \
$(bwrap_srcpath)/bind-mount.c \
$(bwrap_srcpath)/network.h \
$(bwrap_srcpath)/network.c \
$(bwrap_srcpath)/utils.h \
$(bwrap_srcpath)/utils.c \
$(NULL)
bwrap_CFLAGS = $(AM_CFLAGS)
bwrap_LDADD = $(SELINUX_LIBS)

View File

@@ -1,18 +0,0 @@
XSLTPROC = xsltproc
XSLTPROC_FLAGS = \
--nonet \
--stringparam man.output.quietly 1 \
--stringparam funcsynopsis.style ansi \
--stringparam man.th.extra1.suppress 1 \
--stringparam man.authors.section.enabled 0 \
--stringparam man.copyright.section.enabled 0
.xml.1:
$(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
if ENABLE_MAN
man_MANS = bwrap.1
CLEANFILES += $(man_MANS)
endif
EXTRA_DIST += bwrap.xml

View File

@@ -1,102 +0,0 @@
AM_CFLAGS = $(WARN_CFLAGS)
CLEANFILES =
EXTRA_DIST = \
.dir-locals.el \
.editorconfig \
README.md \
autogen.sh \
completions/bash/meson.build \
completions/meson.build \
completions/zsh/meson.build \
demos/bubblewrap-shell.sh \
demos/flatpak-run.sh \
demos/flatpak.bpf \
demos/userns-block-fd.py \
meson.build \
meson_options.txt \
packaging/bubblewrap.spec \
tests/meson.build \
tests/use-as-subproject/README \
tests/use-as-subproject/config.h \
tests/use-as-subproject/dummy-config.h.in \
tests/use-as-subproject/meson.build \
uncrustify.cfg \
uncrustify.sh \
$(NULL)
GITIGNOREFILES = build-aux/ gtk-doc.make config.h.in aclocal.m4
bin_PROGRAMS = bwrap
bwrap_srcpath := $(srcdir)
include Makefile-bwrap.am
install-exec-hook:
if PRIV_MODE_SETUID
$(SUDO_BIN) chown root $(DESTDIR)$(bindir)/bwrap
$(SUDO_BIN) chmod u+s $(DESTDIR)$(bindir)/bwrap
endif
test_programs = \
tests/test-utils \
$(NULL)
test_scripts = \
tests/test-run.sh \
tests/test-seccomp.py \
tests/test-specifying-userns.sh \
tests/test-specifying-pidns.sh \
$(NULL)
test_extra_programs = \
test-bwrap \
tests/try-syscall \
$(NULL)
test-bwrap: bwrap
rm -rf test-bwrap
cp bwrap test-bwrap
chmod 0755 test-bwrap
if PRIV_MODE_SETUID
$(SUDO_BIN) chown root test-bwrap
$(SUDO_BIN) chmod u+s test-bwrap
endif
tests_test_utils_SOURCES = \
tests/test-utils.c \
utils.h \
utils.c \
$(NULL)
tests_test_utils_LDADD = $(SELINUX_LIBS)
test_bwrap_SOURCES=
include Makefile-docs.am
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
LOG_COMPILER =
TESTS_ENVIRONMENT = \
BWRAP=$(abs_top_builddir)/test-bwrap \
G_TEST_BUILDDIR=$(abs_top_builddir) \
G_TEST_SRCDIR=$(abs_top_srcdir) \
$(NULL)
check_PROGRAMS = $(test_programs) $(test_extra_programs)
TESTS = $(test_programs) $(test_scripts)
EXTRA_DIST += $(test_scripts)
EXTRA_DIST += tests/libtest-core.sh
EXTRA_DIST += tests/libtest.sh
if ENABLE_BASH_COMPLETION
bashcompletiondir = $(BASH_COMPLETION_DIR)
dist_bashcompletion_DATA = completions/bash/bwrap
endif
if ENABLE_ZSH_COMPLETION
zshcompletiondir = $(ZSH_COMPLETION_DIR)
dist_zshcompletion_DATA = completions/zsh/_bwrap
endif
-include $(top_srcdir)/git.mk
AM_DISTCHECK_CONFIGURE_FLAGS = \
--with-bash-completion-dir="\$(datadir)"/bash-completion/ \
$(NULL)

51
NEWS.md Normal file
View File

@@ -0,0 +1,51 @@
bubblewrap 0.11.0
=================
Released: 2024-10-30
Dependencies:
* Remove the Autotools build system. Meson ≥ 0.49.0 is now required
at build-time. (#625, Hugo Osvaldo Barrera)
* For users of bash-completion, bash-completion ≥ 2.10 is recommended.
With older bash-completion, bubblewrap might install completions
outside its `${prefix}` unless overridden with `-Dbash_completion_dir=…`.
Enhancements:
* New `--overlay`, `--tmp-overlay`, `--ro-overlay` and `--overlay-src`
options allow creation of overlay mounts.
This feature is not available when bubblewrap is installed setuid.
(#412, #663; Ryan Hendrickson, William Manley, Simon McVittie)
* New `--level-prefix` option produces output that can be parsed by
tools like `logger --prio-prefix` and `systemd-cat --level-prefix=1`
(#646, Simon McVittie)
Bug fixes:
* Handle `EINTR` when doing I/O on files or sockets (#657, Simon McVittie)
* Don't make assumptions about alignment of socket control message data
(#637, Simon McVittie)
* Silence some Meson deprecation warnings (#647, @Sertonix)
* Update URLs in documentation to https (#566, @TotalCaesar659)
* Improve tests' compatibility with busybox (#627, @Sertonix)
* Improve compatibility with Meson < 1.3.0 (#664, Simon McVittie)
Internal changes:
* Consistently use `<stdbool.h>` for booleans (#660, Simon McVittie)
* Avoid `-Wshadow` compiler warnings (#661, Simon McVittie)
* Update Github Actions configuration (#658, Simon McVittie)
----
See also <https://github.com/containers/bubblewrap/releases>

View File

@@ -68,13 +68,16 @@ or an ad-hoc script) is responsible for defining its own security model,
and choosing appropriate bubblewrap command-line arguments to implement
that security model.
Some aspects of sandbox security that require particular care are described
in the [Limitations](#limitations) section below.
Users
-----
This program can be shared by all container tools which perform
non-root operation, such as:
- [Flatpak](http://www.flatpak.org)
- [Flatpak](https://www.flatpak.org)
- [rpm-ostree unprivileged](https://github.com/projectatomic/rpm-ostree/pull/209)
- [bwrap-oci](https://github.com/projectatomic/bwrap-oci)
@@ -89,25 +92,15 @@ Installation
bubblewrap is available in the package repositories of the most Linux distributions
and can be installed from there.
If you need to build bubblewrap from source, you can do this with meson or autotools.
If you need to build bubblewrap from source, you can do this with meson:
meson:
```
```sh
meson _builddir
meson compile -C _builddir
meson test -C _builddir
meson install -C _builddir
```
autotools:
```
./autogen.sh
make
sudo make install
```
Usage
-----
@@ -152,26 +145,50 @@ Any such directories you specify mounted `nodev` by default, and can be made rea
Additionally you can use these kernel features:
User namespaces ([CLONE_NEWUSER](http://linux.die.net/man/2/clone)): This hides all but the current uid and gid from the
User namespaces ([CLONE_NEWUSER](https://linux.die.net/man/2/clone)): This hides all but the current uid and gid from the
sandbox. You can also change what the value of uid/gid should be in the sandbox.
IPC namespaces ([CLONE_NEWIPC](http://linux.die.net/man/2/clone)): The sandbox will get its own copy of all the
IPC namespaces ([CLONE_NEWIPC](https://linux.die.net/man/2/clone)): The sandbox will get its own copy of all the
different forms of IPCs, like SysV shared memory and semaphores.
PID namespaces ([CLONE_NEWPID](http://linux.die.net/man/2/clone)): The sandbox will not see any processes outside the sandbox. Additionally, bubblewrap will run a trivial pid1 inside your container to handle the requirements of reaping children in the sandbox. This avoids what is known now as the [Docker pid 1 problem](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/).
PID namespaces ([CLONE_NEWPID](https://linux.die.net/man/2/clone)): The sandbox will not see any processes outside the sandbox. Additionally, bubblewrap will run a trivial pid1 inside your container to handle the requirements of reaping children in the sandbox. This avoids what is known now as the [Docker pid 1 problem](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/).
Network namespaces ([CLONE_NEWNET](http://linux.die.net/man/2/clone)): The sandbox will not see the network. Instead it will have its own network namespace with only a loopback device.
Network namespaces ([CLONE_NEWNET](https://linux.die.net/man/2/clone)): The sandbox will not see the network. Instead it will have its own network namespace with only a loopback device.
UTS namespace ([CLONE_NEWUTS](http://linux.die.net/man/2/clone)): The sandbox will have its own hostname.
UTS namespace ([CLONE_NEWUTS](https://linux.die.net/man/2/clone)): The sandbox will have its own hostname.
Seccomp filters: You can pass in seccomp filters that limit which syscalls can be done in the sandbox. For more information, see [Seccomp](https://en.wikipedia.org/wiki/Seccomp).
If you are not filtering out `TIOCSTI` commands using seccomp filters,
Limitations
-----------
As noted in the [Sandbox security](#sandbox-security) section above,
the level of protection between the sandboxed processes and the host system
is entirely determined by the arguments passed to bubblewrap.
Some aspects that require special care are noted here.
- If you are not filtering out `TIOCSTI` commands using seccomp filters,
argument `--new-session` is needed to protect against out-of-sandbox
command execution
(see [CVE-2017-5226](https://github.com/containers/bubblewrap/issues/142)).
- Everything mounted into the sandbox can potentially be used to escalate
privileges.
For example, if you bind a D-Bus socket into the sandbox, it can be used to
execute commands via systemd. You can use
[xdg-dbus-proxy](https://github.com/flatpak/xdg-dbus-proxy) to filter
D-Bus communication.
- Some applications deploy their own sandboxing mechanisms, and these can be
restricted by the constraints imposed by bubblewrap's sandboxing.
For example, some web browsers which configure their child proccesses via
seccomp to not have access to the filesystem. If you limit the syscalls and
don't allow the seccomp syscall, a browser cannot apply these restrictions.
Similarly, if these rules were compiled into a file that is not available in
the sandbox, the browser cannot load these rules from this file and cannot
apply these restrictions.
Related project comparison: Firejail
------------------------------------

View File

@@ -1,19 +0,0 @@
#!/bin/sh
test -n "$srcdir" || srcdir=$(dirname "$0")
test -n "$srcdir" || srcdir=.
olddir=$(pwd)
cd "$srcdir"
if ! (autoreconf --version >/dev/null 2>&1); then
echo "*** No autoreconf found, please install it ***"
exit 1
fi
mkdir -p m4
autoreconf --force --install --verbose
cd "$olddir"
test -n "$NOCONFIGURE" || "$srcdir/configure" "$@"

View File

@@ -76,7 +76,7 @@ match_token (const char *token, const char *token_end, const char *str)
if (token == token_end)
return *str == 0;
return FALSE;
return false;
}
static unsigned long
@@ -247,7 +247,7 @@ parse_mountinfo (int proc_fd,
die_with_error ("Can't open /proc/self/mountinfo");
n_lines = count_lines (mountinfo);
lines = xcalloc (n_lines * sizeof (MountInfoLine));
lines = xcalloc (n_lines, sizeof (MountInfoLine));
max_id = 0;
line = mountinfo;
@@ -281,12 +281,12 @@ parse_mountinfo (int proc_fd,
die ("Can't parse mountinfo line");
rest = line + consumed;
rest = skip_token (rest, TRUE); /* mountroot */
rest = skip_token (rest, true); /* mountroot */
mountpoint = rest;
rest = skip_token (rest, FALSE); /* mountpoint */
rest = skip_token (rest, false); /* mountpoint */
mountpoint_end = rest++;
options = rest;
rest = skip_token (rest, FALSE); /* vfs options */
rest = skip_token (rest, false); /* vfs options */
options_end = rest;
*mountpoint_end = 0;
@@ -310,11 +310,11 @@ parse_mountinfo (int proc_fd,
if (root == -1)
{
mount_tab = xcalloc (sizeof (MountInfo) * (1));
mount_tab = xcalloc (1, sizeof (MountInfo));
return steal_pointer (&mount_tab);
}
by_id = xcalloc ((max_id + 1) * sizeof (MountInfoLine*));
by_id = xcalloc (max_id + 1, sizeof (MountInfoLine*));
for (i = 0; i < n_lines; i++)
by_id[lines[i].id] = &lines[i];
@@ -324,7 +324,7 @@ parse_mountinfo (int proc_fd,
MountInfoLine *parent = by_id[this->parent_id];
MountInfoLine **to_sibling;
MountInfoLine *sibling;
bool covered = FALSE;
bool covered = false;
if (!has_path_prefix (this->mountpoint, root_mount))
continue;
@@ -333,7 +333,7 @@ parse_mountinfo (int proc_fd,
continue;
if (strcmp (parent->mountpoint, this->mountpoint) == 0)
parent->covered = TRUE;
parent->covered = true;
to_sibling = &parent->first_child;
sibling = parent->first_child;
@@ -344,7 +344,7 @@ parse_mountinfo (int proc_fd,
* covered by the sibling, and we drop it. */
if (has_path_prefix (this->mountpoint, sibling->mountpoint))
{
covered = TRUE;
covered = true;
break;
}
@@ -366,7 +366,7 @@ parse_mountinfo (int proc_fd,
}
n_mounts = count_mounts (&lines[root]);
mount_tab = xcalloc (sizeof (MountInfo) * (n_mounts + 1));
mount_tab = xcalloc (n_mounts + 1, sizeof (MountInfo));
end_tab = collect_mounts (&mount_tab[0], &lines[root]);
assert (end_tab == &mount_tab[n_mounts]);
@@ -405,7 +405,7 @@ bind_mount (int proc_fd,
if (resolved_dest == NULL)
return BIND_MOUNT_ERROR_REALPATH_DEST;
dest_fd = open (resolved_dest, O_PATH | O_CLOEXEC);
dest_fd = TEMP_FAILURE_RETRY (open (resolved_dest, O_PATH | O_CLOEXEC));
if (dest_fd < 0)
{
if (failing_path != NULL)
@@ -499,7 +499,7 @@ bind_mount_result_to_string (bind_mount_result res,
bool *want_errno_p)
{
char *string = NULL;
bool want_errno = TRUE;
bool want_errno = true;
switch (res)
{
@@ -521,7 +521,7 @@ bind_mount_result_to_string (bind_mount_result res,
case BIND_MOUNT_ERROR_FIND_DEST_MOUNT:
string = xasprintf ("Unable to find \"%s\" in mount table", failing_path);
want_errno = FALSE;
want_errno = false;
break;
case BIND_MOUNT_ERROR_REMOUNT_DEST:
@@ -557,9 +557,12 @@ die_with_bind_result (bind_mount_result res,
...)
{
va_list args;
bool want_errno = TRUE;
bool want_errno = true;
char *message;
if (bwrap_level_prefix)
fprintf (stderr, "<%d>", LOG_ERR);
fprintf (stderr, "bwrap: ");
va_start (args, format);
@@ -571,7 +574,24 @@ die_with_bind_result (bind_mount_result res,
/* message is leaked, but we're exiting unsuccessfully anyway, so ignore */
if (want_errno)
fprintf (stderr, ": %s", strerror (saved_errno));
{
switch (res)
{
case BIND_MOUNT_ERROR_MOUNT:
case BIND_MOUNT_ERROR_REMOUNT_DEST:
case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT:
fprintf (stderr, ": %s", mount_strerror (saved_errno));
break;
case BIND_MOUNT_ERROR_REALPATH_DEST:
case BIND_MOUNT_ERROR_REOPEN_DEST:
case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD:
case BIND_MOUNT_ERROR_FIND_DEST_MOUNT:
case BIND_MOUNT_SUCCESS:
default:
fprintf (stderr, ": %s", strerror (saved_errno));
}
}
fprintf (stderr, "\n");
exit (1);

View File

@@ -44,15 +44,6 @@
#define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */
#endif
#ifndef TEMP_FAILURE_RETRY
#define TEMP_FAILURE_RETRY(expression) \
(__extension__ \
({ long int __result; \
do __result = (long int) (expression); \
while (__result == -1L && errno == EINTR); \
__result; }))
#endif
/* We limit the size of a tmpfs to half the architecture's address space,
* to avoid hitting arbitrary limits in the kernel.
* For example, on at least one x86_64 machine, the actual limit seems to be
@@ -74,19 +65,19 @@ static bool opt_as_pid_1;
static const char *opt_argv0 = NULL;
static const char *opt_chdir_path = NULL;
static bool opt_assert_userns_disabled = FALSE;
static bool opt_disable_userns = FALSE;
static bool opt_unshare_user = FALSE;
static bool opt_unshare_user_try = FALSE;
static bool opt_unshare_pid = FALSE;
static bool opt_unshare_ipc = FALSE;
static bool opt_unshare_net = FALSE;
static bool opt_unshare_uts = FALSE;
static bool opt_unshare_cgroup = FALSE;
static bool opt_unshare_cgroup_try = FALSE;
static bool opt_needs_devpts = FALSE;
static bool opt_new_session = FALSE;
static bool opt_die_with_parent = FALSE;
static bool opt_assert_userns_disabled = false;
static bool opt_disable_userns = false;
static bool opt_unshare_user = false;
static bool opt_unshare_user_try = false;
static bool opt_unshare_pid = false;
static bool opt_unshare_ipc = false;
static bool opt_unshare_net = false;
static bool opt_unshare_uts = false;
static bool opt_unshare_cgroup = false;
static bool opt_unshare_cgroup_try = false;
static bool opt_needs_devpts = false;
static bool opt_new_session = false;
static bool opt_die_with_parent = false;
static uid_t opt_sandbox_uid = -1;
static gid_t opt_sandbox_gid = -1;
static int opt_sync_fd = -1;
@@ -100,8 +91,10 @@ static char *opt_args_data = NULL; /* owned */
static int opt_userns_fd = -1;
static int opt_userns2_fd = -1;
static int opt_pidns_fd = -1;
static int opt_tmp_overlay_count = 0;
static int next_perms = -1;
static size_t next_size_arg = 0;
static int next_overlay_src_count = 0;
#define CAP_TO_MASK_0(x) (1L << ((x) & 31))
#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32)
@@ -131,6 +124,10 @@ typedef enum {
SETUP_BIND_MOUNT,
SETUP_RO_BIND_MOUNT,
SETUP_DEV_BIND_MOUNT,
SETUP_OVERLAY_MOUNT,
SETUP_TMP_OVERLAY_MOUNT,
SETUP_RO_OVERLAY_MOUNT,
SETUP_OVERLAY_SRC,
SETUP_MOUNT_PROC,
SETUP_MOUNT_DEV,
SETUP_MOUNT_TMPFS,
@@ -147,7 +144,7 @@ typedef enum {
typedef enum {
NO_CREATE_DEST = (1 << 0),
ALLOW_NOTEXIST = (2 << 0),
ALLOW_NOTEXIST = (1 << 1),
} SetupOpFlag;
typedef struct _SetupOp SetupOp;
@@ -176,6 +173,7 @@ struct _LockFile
enum {
PRIV_SEP_OP_DONE,
PRIV_SEP_OP_BIND_MOUNT,
PRIV_SEP_OP_OVERLAY_MOUNT,
PRIV_SEP_OP_PROC_MOUNT,
PRIV_SEP_OP_TMPFS_MOUNT,
PRIV_SEP_OP_DEVPTS_MOUNT,
@@ -213,7 +211,7 @@ static Type *last_ ## name = NULL; \
static inline Type * \
_ ## name ## _append_new (void) \
{ \
Type *self = xcalloc (sizeof (Type)); \
Type *self = xcalloc (1, sizeof (Type)); \
\
if (last_ ## name != NULL) \
last_ ## name ->next = self; \
@@ -311,6 +309,7 @@ usage (int ecode, FILE *out)
" --version Print version\n"
" --args FD Parse NUL-separated args from FD\n"
" --argv0 VALUE Set argv[0] to the value VALUE before running the program\n"
" --level-prefix Prepend e.g. <3> to diagnostic messages\n"
" --unshare-all Unshare every namespace we support by default\n"
" --share-net Retain the network namespace (can only combine with --unshare-all)\n"
" --unshare-user Create new user namespace (may be automatically implied if not setuid)\n"
@@ -341,7 +340,14 @@ usage (int ecode, FILE *out)
" --dev-bind-try SRC DEST Equal to --dev-bind but ignores non-existent SRC\n"
" --ro-bind SRC DEST Bind mount the host path SRC readonly on DEST\n"
" --ro-bind-try SRC DEST Equal to --ro-bind but ignores non-existent SRC\n"
" --bind-fd FD DEST Bind open directory or path fd on DEST\n"
" --ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST\n"
" --remount-ro DEST Remount DEST as readonly; does not recursively remount\n"
" --overlay-src SRC Read files from SRC in the following overlay\n"
" --overlay RWSRC WORKDIR DEST Mount overlayfs on DEST, with RWSRC as the host path for writes and\n"
" WORKDIR an empty directory on the same filesystem as RWSRC\n"
" --tmp-overlay DEST Mount overlayfs on DEST, with writes going to an invisible tmpfs\n"
" --ro-overlay DEST Mount overlayfs read-only on DEST\n"
" --exec-label LABEL Exec label for the sandbox\n"
" --file-label LABEL File label for temporary sandbox content\n"
" --proc DEST Mount new procfs on DEST\n"
@@ -474,7 +480,7 @@ report_child_exit_status (int exitc, int setup_finished_fd)
return;
output = xasprintf ("{ \"exit-code\": %i }\n", exitc);
dump_info (opt_json_status_fd, output, FALSE);
dump_info (opt_json_status_fd, output, false);
close (opt_json_status_fd);
opt_json_status_fd = -1;
close (setup_finished_fd);
@@ -596,7 +602,7 @@ do_init (int event_fd, pid_t initial_pid)
for (lock = lock_files; lock != NULL; lock = lock->next)
{
int fd = open (lock->path, O_RDONLY | O_CLOEXEC);
int fd = TEMP_FAILURE_RETRY (open (lock->path, O_RDONLY | O_CLOEXEC));
if (fd == -1)
die_with_error ("Unable to open lock file %s", lock->path);
@@ -607,7 +613,7 @@ do_init (int event_fd, pid_t initial_pid)
.l_len = 0
};
if (fcntl (fd, F_SETLK, &l) < 0)
if (TEMP_FAILURE_RETRY (fcntl (fd, F_SETLK, &l)) < 0)
die_with_error ("Unable to lock file %s", lock->path);
/* Keep fd open to hang on to lock */
@@ -619,12 +625,12 @@ do_init (int event_fd, pid_t initial_pid)
seccomp_programs_apply ();
while (TRUE)
while (true)
{
pid_t child;
int status;
child = wait (&status);
child = TEMP_FAILURE_RETRY (wait (&status));
if (child == initial_pid)
{
initial_exit_status = propagate_exit_status (status);
@@ -635,7 +641,7 @@ do_init (int event_fd, pid_t initial_pid)
int res UNUSED;
val = initial_exit_status + 1;
res = write (event_fd, &val, 8);
res = TEMP_FAILURE_RETRY (write (event_fd, &val, 8));
/* Ignore res, if e.g. the parent died and closed event_fd
we don't want to error out here */
}
@@ -763,16 +769,16 @@ prctl_caps (uint32_t *caps, bool do_cap_bounding, bool do_set_ambient)
*/
for (cap = 0; cap <= CAP_LAST_CAP; cap++)
{
bool keep = FALSE;
bool keep = false;
if (cap < 32)
{
if (CAP_TO_MASK_0 (cap) & caps[0])
keep = TRUE;
keep = true;
}
else
{
if (CAP_TO_MASK_1 (cap) & caps[1])
keep = TRUE;
keep = true;
}
if (keep && do_set_ambient)
@@ -801,11 +807,11 @@ static void
drop_cap_bounding_set (bool drop_all)
{
if (!drop_all)
prctl_caps (requested_caps, TRUE, FALSE);
prctl_caps (requested_caps, true, false);
else
{
uint32_t no_caps[2] = {0, 0};
prctl_caps (no_caps, TRUE, FALSE);
prctl_caps (no_caps, true, false);
}
}
@@ -814,13 +820,13 @@ set_ambient_capabilities (void)
{
if (is_privileged)
return;
prctl_caps (requested_caps, FALSE, TRUE);
prctl_caps (requested_caps, false, true);
}
/* This acquires the privileges that the bwrap will need it to work.
* If bwrap is not setuid, then this does nothing, and it relies on
* unprivileged user namespaces to be used. This case is
* "is_privileged = FALSE".
* "is_privileged = false".
*
* If bwrap is setuid, then we do things in phases.
* The first part is run as euid 0, but with fsuid as the real user.
@@ -844,7 +850,7 @@ acquire_privs (void)
if (euid != 0)
die ("Unexpected setuid user %d, should be 0", euid);
is_privileged = TRUE;
is_privileged = true;
/* We want to keep running as euid=0 until at the clone()
* operation because doing so will make the user namespace be
* owned by root, which makes it not ptrace:able by the user as
@@ -865,7 +871,7 @@ acquire_privs (void)
die ("Unable to set fsuid (was %d)", (int)new_fsuid);
/* We never need capabilities after execve(), so lets drop everything from the bounding set */
drop_cap_bounding_set (TRUE);
drop_cap_bounding_set (true);
/* Keep only the required capabilities for setup */
set_required_caps ();
@@ -902,7 +908,7 @@ switch_to_user_with_privs (void)
{
/* If we're in a new user namespace, we got back the bounding set, clear it again */
if (opt_unshare_user || opt_userns_fd != -1)
drop_cap_bounding_set (FALSE);
drop_cap_bounding_set (false);
/* If we switched to a new user namespace it may allow other uids/gids, so switch to the target one */
if (opt_userns_fd != -1)
@@ -1059,10 +1065,10 @@ privileged_op (int privileged_op_socket,
if (arg2 != NULL)
strcpy ((char *) buffer + arg2_offset, arg2);
if (write (privileged_op_socket, buffer, buffer_size) != (ssize_t)buffer_size)
if (TEMP_FAILURE_RETRY (write (privileged_op_socket, buffer, buffer_size)) != (ssize_t)buffer_size)
die ("Can't write to privileged_op_socket");
if (read (privileged_op_socket, buffer, 1) != 1)
if (TEMP_FAILURE_RETRY (read (privileged_op_socket, buffer, 1)) != 1)
die ("Can't read from privileged_op_socket");
return;
@@ -1113,7 +1119,7 @@ privileged_op (int privileged_op_socket,
case PRIV_SEP_OP_PROC_MOUNT:
if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0)
die_with_error ("Can't mount proc on %s", arg1);
die_with_mount_error ("Can't mount proc on %s", arg1);
break;
case PRIV_SEP_OP_TMPFS_MOUNT:
@@ -1132,19 +1138,33 @@ privileged_op (int privileged_op_socket,
cleanup_free char *opt = label_mount (mode, opt_file_label);
if (mount ("tmpfs", arg1, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0)
die_with_error ("Can't mount tmpfs on %s", arg1);
die_with_mount_error ("Can't mount tmpfs on %s", arg1);
break;
}
case PRIV_SEP_OP_DEVPTS_MOUNT:
if (mount ("devpts", arg1, "devpts", MS_NOSUID | MS_NOEXEC,
"newinstance,ptmxmode=0666,mode=620") != 0)
die_with_error ("Can't mount devpts on %s", arg1);
die_with_mount_error ("Can't mount devpts on %s", arg1);
break;
case PRIV_SEP_OP_MQUEUE_MOUNT:
if (mount ("mqueue", arg1, "mqueue", 0, NULL) != 0)
die_with_error ("Can't mount mqueue on %s", arg1);
die_with_mount_error ("Can't mount mqueue on %s", arg1);
break;
case PRIV_SEP_OP_OVERLAY_MOUNT:
if (mount ("overlay", arg2, "overlay", MS_MGC_VAL, arg1) != 0)
{
/* The standard message for ELOOP, "Too many levels of symbolic
* links", is not helpful here. */
if (errno == ELOOP)
die ("Can't make overlay mount on %s with options %s: "
"Overlay directories may not overlap",
arg2, arg1);
die_with_mount_error ("Can't make overlay mount on %s with options %s",
arg2, arg1);
}
break;
case PRIV_SEP_OP_SET_HOSTNAME:
@@ -1170,6 +1190,7 @@ setup_newroot (bool unshare_pid,
int privileged_op_socket)
{
SetupOp *op;
int tmp_overlay_idx = 0;
for (op = ops; op != NULL; op = op->next)
{
@@ -1209,7 +1230,7 @@ setup_newroot (bool unshare_pid,
parent_mode &= ~0005U;
dest = get_newroot_path (op->dest);
if (mkdir_with_parents (dest, parent_mode, FALSE) != 0)
if (mkdir_with_parents (dest, parent_mode, false) != 0)
die_with_error ("Can't mkdir parents for %s", op->dest);
}
@@ -1231,6 +1252,71 @@ setup_newroot (bool unshare_pid,
(op->type == SETUP_RO_BIND_MOUNT ? BIND_READONLY : 0) |
(op->type == SETUP_DEV_BIND_MOUNT ? BIND_DEVICES : 0),
0, 0, source, dest);
if (op->fd >= 0)
{
struct stat fd_st, mount_st;
/* When using bind-fd, there is a race condition between resolving the fd as a magic symlink
* and mounting it, where someone could replace what is at the symlink target. Ideally
* we would not even resolve the symlink and directly bind-mount from the fd, but unfortunately
* we can't do that, because its not permitted to bind mount a fd from another user namespace.
* So, we resolve, mount and then compare fstat+stat to detect the race. */
if (fstat(op->fd, &fd_st) != 0)
die_with_error("Can't stat fd %d", op->fd);
if (lstat(dest, &mount_st) != 0)
die_with_error("Can't stat mount at %s", dest);
if (fd_st.st_ino != mount_st.st_ino ||
fd_st.st_dev != mount_st.st_dev)
die_with_error("Race condition binding dirfd");
close(op->fd);
op->fd = -1;
}
break;
case SETUP_OVERLAY_MOUNT:
case SETUP_RO_OVERLAY_MOUNT:
case SETUP_TMP_OVERLAY_MOUNT:
{
StringBuilder sb = {0};
bool multi_src = false;
if (ensure_dir (dest, 0755) != 0)
die_with_error ("Can't mkdir %s", op->dest);
if (op->source != NULL)
{
strappend (&sb, "upperdir=/oldroot");
strappend_escape_for_mount_options (&sb, op->source);
strappend (&sb, ",workdir=/oldroot");
op = op->next;
strappend_escape_for_mount_options (&sb, op->source);
strappend (&sb, ",");
}
else if (op->type == SETUP_TMP_OVERLAY_MOUNT)
strappendf (&sb, "upperdir=/tmp-overlay-upper-%1$d,workdir=/tmp-overlay-work-%1$d,",
tmp_overlay_idx++);
strappend (&sb, "lowerdir=/oldroot");
while (op->next != NULL && op->next->type == SETUP_OVERLAY_SRC)
{
op = op->next;
if (multi_src)
strappend (&sb, ":/oldroot");
strappend_escape_for_mount_options (&sb, op->source);
multi_src = true;
}
strappend (&sb, ",userxattr");
privileged_op (privileged_op_socket,
PRIV_SEP_OP_OVERLAY_MOUNT, 0, 0, 0, sb.str, dest);
free (sb.str);
}
break;
case SETUP_REMOUNT_RO_NO_RECURSIVE:
@@ -1468,7 +1554,25 @@ setup_newroot (bool unshare_pid,
case SETUP_MAKE_SYMLINK:
assert (op->source != NULL); /* guaranteed by the constructor */
if (symlink (op->source, dest) != 0)
die_with_error ("Can't make symlink at %s", op->dest);
{
if (errno == EEXIST)
{
cleanup_free char *existing = readlink_malloc (dest);
if (existing == NULL)
{
if (errno == EINVAL)
die ("Can't make symlink at %s: destination exists and is not a symlink", op->dest);
else
die_with_error ("Can't make symlink at %s: destination exists, and cannot read symlink target", op->dest);
}
if (strcmp (existing, op->source) == 0)
break;
die ("Can't make symlink at %s: existing destination is %s", op->dest, existing);
}
die_with_error ("Can't make symlink at %s", op->dest);
}
break;
case SETUP_SET_HOSTNAME:
@@ -1478,6 +1582,7 @@ setup_newroot (bool unshare_pid,
op->dest, NULL);
break;
case SETUP_OVERLAY_SRC: /* handled by SETUP_OVERLAY_MOUNT */
default:
die ("Unexpected type %d", op->type);
}
@@ -1520,6 +1625,8 @@ resolve_symlinks_in_ops (void)
case SETUP_RO_BIND_MOUNT:
case SETUP_DEV_BIND_MOUNT:
case SETUP_BIND_MOUNT:
case SETUP_OVERLAY_SRC:
case SETUP_OVERLAY_MOUNT:
old_source = op->source;
op->source = realpath (old_source, NULL);
if (op->source == NULL)
@@ -1531,6 +1638,8 @@ resolve_symlinks_in_ops (void)
}
break;
case SETUP_RO_OVERLAY_MOUNT:
case SETUP_TMP_OVERLAY_MOUNT:
case SETUP_MOUNT_PROC:
case SETUP_MOUNT_DEV:
case SETUP_MOUNT_TMPFS:
@@ -1622,6 +1731,32 @@ warn_only_last_option (const char *name)
warn ("Only the last %s option will take effect", name);
}
static void
make_setup_overlay_src_ops (const char *const *const argv)
{
/* SETUP_OVERLAY_SRC is unlike other SETUP_* ops in that it exists to hold
* data for SETUP_{,TMP_,RO_}OVERLAY_MOUNT ops, not to be its own operation.
* This lets us reuse existing code paths to handle resolving the realpaths
* of each source, as no other operations involve multiple sources the way
* the *_OVERLAY_MOUNT ops do.
*
* While the --overlay-src arguments are expected to (directly) precede the
* --overlay argument, in bottom-to-top order, the SETUP_OVERLAY_SRC ops
* follow their corresponding *_OVERLAY_MOUNT op, in top-to-bottom order
* (the order in which overlayfs will want them). They are handled specially
* in setup_new_root () during the processing of *_OVERLAY_MOUNT.
*/
int i;
SetupOp *op;
for (i = 1; i <= next_overlay_src_count; i++)
{
op = setup_op_new (SETUP_OVERLAY_SRC);
op->source = argv[1 - 2 * i];
}
next_overlay_src_count = 0;
}
static void
parse_args_recurse (int *argcp,
const char ***argvp,
@@ -1702,7 +1837,7 @@ parse_args_recurse (int *argcp,
p++;
}
data_argv = xcalloc (sizeof (char *) * (data_argc + 1));
data_argv = xcalloc (data_argc + 1, sizeof (char *));
i = 0;
p = opt_args_data;
@@ -1717,7 +1852,7 @@ parse_args_recurse (int *argcp,
}
data_argv_copy = data_argv; /* Don't change data_argv, we need to free it */
parse_args_recurse (&data_argc, &data_argv_copy, TRUE, total_parsed_argc_p);
parse_args_recurse (&data_argc, &data_argv_copy, true, total_parsed_argc_p);
argv += 1;
argc -= 1;
@@ -1734,6 +1869,10 @@ parse_args_recurse (int *argcp,
argv++;
argc--;
}
else if (strcmp (arg, "--level-prefix") == 0)
{
bwrap_level_prefix = true;
}
else if (strcmp (arg, "--unshare-all") == 0)
{
/* Keep this in order with the older (legacy) --unshare arguments,
@@ -1742,45 +1881,45 @@ parse_args_recurse (int *argcp,
*/
opt_unshare_user_try = opt_unshare_ipc = opt_unshare_pid =
opt_unshare_uts = opt_unshare_cgroup_try =
opt_unshare_net = TRUE;
opt_unshare_net = true;
}
/* Begin here the older individual --unshare variants */
else if (strcmp (arg, "--unshare-user") == 0)
{
opt_unshare_user = TRUE;
opt_unshare_user = true;
}
else if (strcmp (arg, "--unshare-user-try") == 0)
{
opt_unshare_user_try = TRUE;
opt_unshare_user_try = true;
}
else if (strcmp (arg, "--unshare-ipc") == 0)
{
opt_unshare_ipc = TRUE;
opt_unshare_ipc = true;
}
else if (strcmp (arg, "--unshare-pid") == 0)
{
opt_unshare_pid = TRUE;
opt_unshare_pid = true;
}
else if (strcmp (arg, "--unshare-net") == 0)
{
opt_unshare_net = TRUE;
opt_unshare_net = true;
}
else if (strcmp (arg, "--unshare-uts") == 0)
{
opt_unshare_uts = TRUE;
opt_unshare_uts = true;
}
else if (strcmp (arg, "--unshare-cgroup") == 0)
{
opt_unshare_cgroup = TRUE;
opt_unshare_cgroup = true;
}
else if (strcmp (arg, "--unshare-cgroup-try") == 0)
{
opt_unshare_cgroup_try = TRUE;
opt_unshare_cgroup_try = true;
}
/* Begin here the newer --share variants */
else if (strcmp (arg, "--share-net") == 0)
{
opt_unshare_net = FALSE;
opt_unshare_net = false;
}
/* End --share variants, other arguments begin */
else if (strcmp (arg, "--chdir") == 0)
@@ -1797,11 +1936,11 @@ parse_args_recurse (int *argcp,
}
else if (strcmp (arg, "--disable-userns") == 0)
{
opt_disable_userns = TRUE;
opt_disable_userns = true;
}
else if (strcmp (arg, "--assert-userns-disabled") == 0)
{
opt_assert_userns_disabled = TRUE;
opt_assert_userns_disabled = true;
}
else if (strcmp (arg, "--remount-ro") == 0)
{
@@ -1859,6 +1998,100 @@ parse_args_recurse (int *argcp,
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--bind-fd") == 0 ||
strcmp (arg, "--ro-bind-fd") == 0)
{
int src_fd;
char *endptr;
if (argc < 3)
die ("--bind-fd takes two arguments");
src_fd = strtol (argv[1], &endptr, 10);
if (argv[1][0] == 0 || endptr[0] != 0 || src_fd < 0)
die ("Invalid fd: %s", argv[1]);
if (strcmp(arg, "--ro-bind-fd") == 0)
op = setup_op_new (SETUP_RO_BIND_MOUNT);
else
op = setup_op_new (SETUP_BIND_MOUNT);
op->source = xasprintf ("/proc/self/fd/%d", src_fd);
op->fd = src_fd;
op->dest = argv[2];
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--overlay-src") == 0)
{
if (is_privileged)
die ("The --overlay-src option is not permitted in setuid mode");
next_overlay_src_count++;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--overlay") == 0)
{
SetupOp *workdir_op;
if (is_privileged)
die ("The --overlay option is not permitted in setuid mode");
if (argc < 4)
die ("--overlay takes three arguments");
if (next_overlay_src_count < 1)
die ("--overlay requires at least one --overlay-src");
op = setup_op_new (SETUP_OVERLAY_MOUNT);
op->source = argv[1];
workdir_op = setup_op_new (SETUP_OVERLAY_SRC);
workdir_op->source = argv[2];
op->dest = argv[3];
make_setup_overlay_src_ops (argv);
argv += 3;
argc -= 3;
}
else if (strcmp (arg, "--tmp-overlay") == 0)
{
if (is_privileged)
die ("The --tmp-overlay option is not permitted in setuid mode");
if (argc < 2)
die ("--tmp-overlay takes an argument");
if (next_overlay_src_count < 1)
die ("--tmp-overlay requires at least one --overlay-src");
op = setup_op_new (SETUP_TMP_OVERLAY_MOUNT);
op->dest = argv[1];
make_setup_overlay_src_ops (argv);
opt_tmp_overlay_count++;
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--ro-overlay") == 0)
{
if (is_privileged)
die ("The --ro-overlay option is not permitted in setuid mode");
if (argc < 2)
die ("--ro-overlay takes an argument");
if (next_overlay_src_count < 2)
die ("--ro-overlay requires at least two --overlay-src");
op = setup_op_new (SETUP_RO_OVERLAY_MOUNT);
op->dest = argv[1];
make_setup_overlay_src_ops (argv);
argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--proc") == 0)
{
if (argc < 2)
@@ -1907,7 +2140,7 @@ parse_args_recurse (int *argcp,
op = setup_op_new (SETUP_MOUNT_DEV);
op->dest = argv[1];
opt_needs_devpts = TRUE;
opt_needs_devpts = true;
argv += 1;
argc -= 1;
@@ -2357,15 +2590,15 @@ parse_args_recurse (int *argcp,
}
else if (strcmp (arg, "--new-session") == 0)
{
opt_new_session = TRUE;
opt_new_session = true;
}
else if (strcmp (arg, "--die-with-parent") == 0)
{
opt_die_with_parent = TRUE;
opt_die_with_parent = true;
}
else if (strcmp (arg, "--as-pid-1") == 0)
{
opt_as_pid_1 = TRUE;
opt_as_pid_1 = true;
}
else if (strcmp (arg, "--cap-add") == 0)
{
@@ -2373,7 +2606,7 @@ parse_args_recurse (int *argcp,
if (argc < 2)
die ("--cap-add takes an argument");
opt_cap_add_or_drop_used = TRUE;
opt_cap_add_or_drop_used = true;
if (strcasecmp (argv[1], "ALL") == 0)
{
@@ -2399,7 +2632,7 @@ parse_args_recurse (int *argcp,
if (argc < 2)
die ("--cap-drop takes an argument");
opt_cap_add_or_drop_used = TRUE;
opt_cap_add_or_drop_used = true;
if (strcasecmp (argv[1], "ALL") == 0)
{
@@ -2528,6 +2761,10 @@ parse_args_recurse (int *argcp,
if (!is_modifier_option(arg) && next_size_arg != 0)
die ("--size must be followed by --tmpfs");
/* Similarly for --overlay-src. */
if (strcmp (arg, "--overlay-src") != 0 && next_overlay_src_count > 0)
die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay");
argv++;
argc--;
}
@@ -2542,7 +2779,10 @@ parse_args (int *argcp,
{
int total_parsed_argc = *argcp;
parse_args_recurse (argcp, argvp, FALSE, &total_parsed_argc);
parse_args_recurse (argcp, argvp, false, &total_parsed_argc);
if (next_overlay_src_count > 0)
die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay");
}
static void
@@ -2576,7 +2816,7 @@ namespace_ids_read (pid_t pid)
NsInfo *info;
dir = xasprintf ("%d/ns", pid);
ns_fd = openat (proc_fd, dir, O_PATH);
ns_fd = TEMP_FAILURE_RETRY (openat (proc_fd, dir, O_PATH));
if (ns_fd < 0)
die_with_error ("open /proc/%s/ns failed", dir);
@@ -2588,7 +2828,7 @@ namespace_ids_read (pid_t pid)
int r;
/* if we don't unshare this ns, ignore it */
if (do_unshare && *do_unshare == FALSE)
if (do_unshare && *do_unshare == false)
continue;
r = fstatat (ns_fd, info->name, &st, 0);
@@ -2623,7 +2863,7 @@ namespace_ids_write (int fd,
output = xasprintf (",%s\"%s-namespace\": %ju",
indent, info->name, nsid);
dump_info (fd, output, TRUE);
dump_info (fd, output, true);
}
}
@@ -2648,6 +2888,7 @@ main (int argc,
cleanup_free char *args_data UNUSED = NULL;
int intermediate_pids_sockets[2] = {-1, -1};
const char *exec_path = NULL;
int i;
/* Handle --version early on before we try to acquire/drop
* any capabilities so it works in a build environment;
@@ -2731,18 +2972,18 @@ main (int argc,
/* We have to do this if we weren't installed setuid (and we're not
* root), so let's just DWIM */
if (!is_privileged && getuid () != 0 && opt_userns_fd == -1)
opt_unshare_user = TRUE;
opt_unshare_user = true;
#ifdef ENABLE_REQUIRE_USERNS
/* In this build option, we require userns. */
if (is_privileged && getuid () != 0 && opt_userns_fd == -1)
opt_unshare_user = TRUE;
opt_unshare_user = true;
#endif
if (opt_unshare_user_try &&
stat ("/proc/self/ns/user", &sbuf) == 0)
{
bool disabled = FALSE;
bool disabled = false;
/* RHEL7 has a kernel module parameter that lets you enable user namespaces */
if (stat ("/sys/module/user_namespace/parameters/enable", &sbuf) == 0)
@@ -2750,7 +2991,7 @@ main (int argc,
cleanup_free char *enable = NULL;
enable = load_file_at (AT_FDCWD, "/sys/module/user_namespace/parameters/enable");
if (enable != NULL && enable[0] == 'N')
disabled = TRUE;
disabled = true;
}
/* Check for max_user_namespaces */
@@ -2759,21 +3000,21 @@ main (int argc,
cleanup_free char *max_user_ns = NULL;
max_user_ns = load_file_at (AT_FDCWD, "/proc/sys/user/max_user_namespaces");
if (max_user_ns != NULL && strcmp(max_user_ns, "0\n") == 0)
disabled = TRUE;
disabled = true;
}
/* Debian lets you disable *unprivileged* user namespaces. However this is not
a problem if we're privileged, and if we're not opt_unshare_user is TRUE
a problem if we're privileged, and if we're not opt_unshare_user is true
already, and there is not much we can do, its just a non-working setup. */
if (!disabled)
opt_unshare_user = TRUE;
opt_unshare_user = true;
}
if (argc <= 0)
usage (EXIT_FAILURE, stderr);
__debug__ (("Creating root mount point\n"));
debug ("Creating root mount point");
if (opt_sandbox_uid == (uid_t)-1)
opt_sandbox_uid = real_uid;
@@ -2797,7 +3038,7 @@ main (int argc,
/* We need to read stuff from proc during the pivot_root dance, etc.
Lets keep a fd to it open */
proc_fd = open ("/proc", O_PATH);
proc_fd = TEMP_FAILURE_RETRY (open ("/proc", O_PATH));
if (proc_fd == -1)
die_with_error ("Can't open /proc");
@@ -2809,7 +3050,7 @@ main (int argc,
* access ourselves. */
base_path = "/tmp";
__debug__ (("creating new namespace\n"));
debug ("creating new namespace");
if (opt_unshare_pid && !opt_as_pid_1)
{
@@ -2925,7 +3166,7 @@ main (int argc,
*/
write_uid_gid_map (ns_uid, real_uid,
ns_gid, real_gid,
pid, TRUE, opt_needs_devpts);
pid, true, opt_needs_devpts);
}
/* Initial launched process, wait for pid 1 or exec:ed command to exit */
@@ -2934,7 +3175,7 @@ main (int argc,
die_with_error ("Setting userns2 failed");
/* We don't need any privileges in the launcher, drop them immediately. */
drop_privs (FALSE, FALSE);
drop_privs (false, false);
/* Optionally bind our lifecycle to that of the parent */
handle_die_with_parent ();
@@ -2942,17 +3183,17 @@ main (int argc,
if (opt_info_fd != -1)
{
cleanup_free char *output = xasprintf ("{\n \"child-pid\": %i", pid);
dump_info (opt_info_fd, output, TRUE);
namespace_ids_write (opt_info_fd, FALSE);
dump_info (opt_info_fd, "\n}\n", TRUE);
dump_info (opt_info_fd, output, true);
namespace_ids_write (opt_info_fd, false);
dump_info (opt_info_fd, "\n}\n", true);
close (opt_info_fd);
}
if (opt_json_status_fd != -1)
{
cleanup_free char *output = xasprintf ("{ \"child-pid\": %i", pid);
dump_info (opt_json_status_fd, output, TRUE);
namespace_ids_write (opt_json_status_fd, TRUE);
dump_info (opt_json_status_fd, " }\n", TRUE);
dump_info (opt_json_status_fd, output, true);
namespace_ids_write (opt_json_status_fd, true);
dump_info (opt_json_status_fd, " }\n", true);
}
if (opt_userns_block_fd != -1)
@@ -2964,7 +3205,7 @@ main (int argc,
/* Let child run now that the uid maps are set up */
val = 1;
res = write (child_wait_fd, &val, 8);
res = TEMP_FAILURE_RETRY (write (child_wait_fd, &val, 8));
/* Ignore res, if e.g. the child died and closed child_wait_fd we don't want to error out here */
close (child_wait_fd);
@@ -3048,7 +3289,7 @@ main (int argc,
write_uid_gid_map (ns_uid, real_uid,
ns_gid, real_gid,
-1, TRUE, FALSE);
-1, true, false);
}
old_umask = umask (0);
@@ -3060,11 +3301,11 @@ main (int argc,
* receive mounts from the real root, but don't
* propagate mounts to the real root. */
if (mount (NULL, "/", NULL, MS_SILENT | MS_SLAVE | MS_REC, NULL) < 0)
die_with_error ("Failed to make / slave");
die_with_mount_error ("Failed to make / slave");
/* Create a tmpfs which we will use as / in the namespace */
if (mount ("tmpfs", base_path, "tmpfs", MS_NODEV | MS_NOSUID, NULL) != 0)
die_with_error ("Failed to mount tmpfs");
die_with_mount_error ("Failed to mount tmpfs");
old_cwd = get_current_dir_name ();
@@ -3083,11 +3324,24 @@ main (int argc,
die_with_error ("Creating newroot failed");
if (mount ("newroot", "newroot", NULL, MS_SILENT | MS_MGC_VAL | MS_BIND | MS_REC, NULL) < 0)
die_with_error ("setting up newroot bind");
die_with_mount_error ("setting up newroot bind");
if (mkdir ("oldroot", 0755))
die_with_error ("Creating oldroot failed");
for (i = 0; i < opt_tmp_overlay_count; i++)
{
char *dirname;
dirname = xasprintf ("tmp-overlay-upper-%d", i);
if (mkdir (dirname, 0755))
die_with_error ("Creating --tmp-overlay upperdir failed");
free (dirname);
dirname = xasprintf ("tmp-overlay-work-%d", i);
if (mkdir (dirname, 0755))
die_with_error ("Creating --tmp-overlay workdir failed");
free (dirname);
}
if (pivot_root (base_path, "oldroot"))
die_with_error ("pivot_root");
@@ -3109,7 +3363,7 @@ main (int argc,
if (child == 0)
{
/* Unprivileged setup process */
drop_privs (FALSE, TRUE);
drop_privs (false, true);
close (privsep_sockets[0]);
setup_newroot (opt_unshare_pid, privsep_sockets[1]);
exit (0);
@@ -3131,12 +3385,12 @@ main (int argc,
op = read_priv_sec_op (unpriv_socket, buffer, sizeof (buffer),
&flags, &perms, &size_arg, &arg1, &arg2);
privileged_op (-1, op, flags, perms, size_arg, arg1, arg2);
if (write (unpriv_socket, buffer, 1) != 1)
if (TEMP_FAILURE_RETRY (write (unpriv_socket, buffer, 1)) != 1)
die ("Can't write to op_socket");
}
while (op != PRIV_SEP_OP_DONE);
waitpid (child, &status, 0);
TEMP_FAILURE_RETRY (waitpid (child, &status, 0));
/* Continue post setup */
}
}
@@ -3149,7 +3403,7 @@ main (int argc,
/* The old root better be rprivate or we will send unmount events to the parent namespace */
if (mount ("oldroot", "oldroot", NULL, MS_SILENT | MS_REC | MS_PRIVATE, NULL) != 0)
die_with_error ("Failed to make old root rprivate");
die_with_mount_error ("Failed to make old root rprivate");
if (umount2 ("oldroot", MNT_DETACH))
die_with_error ("unmount old root");
@@ -3160,7 +3414,7 @@ main (int argc,
* We're aiming to make /newroot the real root, and get rid of /oldroot. To do
* that we need a temporary place to store it before we can unmount it.
*/
{ cleanup_fd int oldrootfd = open ("/", O_DIRECTORY | O_RDONLY);
{ cleanup_fd int oldrootfd = TEMP_FAILURE_RETRY (open ("/", O_DIRECTORY | O_RDONLY));
if (oldrootfd < 0)
die_with_error ("can't open /");
if (chdir ("/newroot") != 0)
@@ -3208,7 +3462,7 @@ main (int argc,
{
cleanup_fd int sysctl_fd = -1;
sysctl_fd = openat (proc_fd, "sys/user/max_user_namespaces", O_WRONLY);
sysctl_fd = TEMP_FAILURE_RETRY (openat (proc_fd, "sys/user/max_user_namespaces", O_WRONLY));
if (sysctl_fd < 0)
die_with_error ("cannot open /proc/sys/user/max_user_namespaces");
@@ -3221,11 +3475,11 @@ main (int argc,
die_with_error ("unshare user ns");
/* We're in a new user namespace, we got back the bounding set, clear it again */
drop_cap_bounding_set (FALSE);
drop_cap_bounding_set (false);
write_uid_gid_map (opt_sandbox_uid, ns_uid,
opt_sandbox_gid, ns_gid,
-1, FALSE, FALSE);
-1, false, false);
}
if (opt_disable_userns || opt_assert_userns_disabled)
@@ -3238,7 +3492,7 @@ main (int argc,
}
/* All privileged ops are done now, so drop caps we don't need */
drop_privs (!is_privileged, TRUE);
drop_privs (!is_privileged, true);
if (opt_block_fd != -1)
{
@@ -3286,7 +3540,7 @@ main (int argc,
if (label_exec (opt_exec_label) == -1)
die_with_error ("label_exec %s", argv[0]);
__debug__ (("forking for child\n"));
debug ("forking for child");
if (!opt_as_pid_1 && (opt_unshare_pid || lock_files != NULL || opt_sync_fd != -1))
{
@@ -3302,7 +3556,7 @@ main (int argc,
if (pid != 0)
{
drop_all_caps (FALSE);
drop_all_caps (false);
/* Close fds in pid 1, except stdio and optionally event_fd
(for syncing pid 2 lifetime with monitor_child) and
@@ -3324,7 +3578,7 @@ main (int argc,
}
}
__debug__ (("launch executable %s\n", argv[0]));
debug ("launch executable %s", argv[0]);
if (proc_fd != -1)
close (proc_fd);

111
bwrap.xml
View File

@@ -96,6 +96,26 @@
<term><option>--argv0 <arg choice="plain">VALUE</arg></option></term>
<listitem><para>Set argv[0] to the value <arg choice="plain">VALUE</arg> before running the program</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--level-prefix</option></term>
<listitem>
<para>
Prefix each line of diagnostic output with a numeric severity
level enclosed in angle brackets.
The severity levels used are based on the constants used by
<citerefentry><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>:
for example, <literal>&lt;4&gt;</literal> indicates a warning,
because <literal>LOG_WARNING</literal> has numeric value 4.
Numbers smaller than 4 indicate fatal errors, and numbers larger
than 4 indicate informational messages.
These prefixes can be parsed by tools compatible with
<literal>logger --prio-prefix</literal> (see
<citerefentry><refentrytitle>logger</refentrytitle><manvolnum>1</manvolnum></citerefentry>)
or <literal>systemd-cat --level-prefix=1</literal> (see
<citerefentry><refentrytitle>systemd-cat</refentrytitle><manvolnum>1</manvolnum></citerefentry>).
</para>
</listitem>
</varlistentry>
</variablelist>
<para>Options related to kernel namespaces:</para>
<variablelist>
@@ -297,6 +317,86 @@
<term><option>--remount-ro <arg choice="plain">DEST</arg></option></term>
<listitem><para>Remount the path <arg choice="plain">DEST</arg> as readonly. It works only on the specified mount point, without changing any other mount point under the specified path</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--overlay-src <arg choice="plain">SRC</arg></option></term>
<listitem>
<para>
This option does nothing on its own, and must be followed by one of
the other <literal>overlay</literal> options. It specifies a host
path from which files should be read if they aren't present in a
higher layer.
</para>
<para>
This option can be used multiple times to provide multiple sources.
The sources are overlaid in the order given, with the first source on
the command line at the bottom of the stack: if a given path to be
read exists in more than one source, the file is read from the last
such source specified.
</para>
<para>
(For readers familiar with overlayfs, note that this is the
reverse of the order used by the kernel's <literal>lowerdir</literal>
mount option.)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--overlay <arg choice="plain">RWSRC</arg> <arg choice="plain">WORKDIR</arg> <arg choice="plain">DEST</arg></option></term>
</varlistentry>
<varlistentry>
<term><option>--tmp-overlay <arg choice="plain">DEST</arg></option></term>
</varlistentry>
<varlistentry>
<term><option>--ro-overlay <arg choice="plain">DEST</arg></option></term>
<listitem>
<para>
Use overlayfs to mount the host paths specified by
<arg choice="plain">RWSRC</arg> and all immediately preceding
<option>--overlay-src</option> on <arg choice="plain">DEST</arg>.
<arg choice="plain">DEST</arg> will contain the union of all the files
in all the layers.
</para>
<para>
With <arg choice="plain">--overlay</arg> all writes will go to
<arg choice="plain">RWSRC</arg>. Reads will come preferentially from
<arg choice="plain">RWSRC</arg>, and then from any
<option>--overlay-src</option> paths.
<arg choice="plain">WORKDIR</arg> must be an empty directory on the
same filesystem as <arg choice="plain">RWSRC</arg>, and is used
internally by the kernel.
</para>
<para>
With <arg choice="plain">--tmp-overlay</arg> all writes will go to
the tmpfs that hosts the sandbox root, in a location not accessible
from either the host or the child process. Writes will therefore not
be persisted across multiple runs.
</para>
<para>
With <arg choice="plain">--ro-overlay</arg> the filesystem will be
mounted read-only. This option requires at least two
<option>--overlay-src</option> to precede it.
</para>
<para>
None of these options are available in the setuid version of
bubblewrap. Using <arg choice="plain">--ro-overlay</arg> or providing
more than one <option>--overlay-src</option> requires a Linux kernel
version of 4.0 or later.
</para>
<para>
Due to limitations of overlayfs, no host directory given via
<arg choice="plain">--overlay-src</arg> or
<arg choice="plain">--overlay</arg> may be an ancestor of another,
after resolving symlinks. Depending on version, the Linux kernel may
or may not enforce this, but if not then overlayfs's behavior is
undefined.
</para>
<para>
For more information see the Overlay Filesystem documentation in the
Linux kernel at
https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--proc <arg choice="plain">DEST</arg></option></term>
<listitem><para>Mount procfs on <arg choice="plain">DEST</arg></para></listitem>
@@ -364,7 +464,16 @@
</varlistentry>
<varlistentry>
<term><option>--symlink <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
<listitem><para>Create a symlink at <arg choice="plain">DEST</arg> with target <arg choice="plain">SRC</arg></para></listitem>
<listitem>
<para>Create a symlink at <arg choice="plain">DEST</arg> with target
<arg choice="plain">SRC</arg>.</para>
<para>Since version 0.9.0, it is not considered to be an error if
<arg choice="plain">DEST</arg> already exists as a symbolic link and its
target is exactly <arg choice="plain">SRC</arg>.</para>
<para>Before version 0.9.0, if <arg choice="plain">DEST</arg> already
existed, this would be treated as an error (even if its target
was identical to <arg choice="plain">SRC</arg>).</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--chmod <arg choice="plain">OCTAL</arg> <arg choice="plain">PATH</arg></option></term>

View File

@@ -56,8 +56,6 @@ done
if dpkg-vendor --derives-from Debian; then
apt-get -y update
apt-get -q -y install \
autoconf \
automake \
build-essential \
docbook-xml \
docbook-xsl \
@@ -81,8 +79,6 @@ if command -v yum; then
yum -y install \
'pkgconfig(libselinux)' \
/usr/bin/eu-readelf \
autoconf \
automake \
docbook-style-xsl \
gcc \
git \

View File

@@ -51,15 +51,19 @@ _bwrap() {
--hostname
--info-fd
--lock-file
--overlay
--overlay-src
--perms
--proc
--remount-ro
--ro-bind
--ro-overlay
--seccomp
--setenv
--size
--symlink
--sync-fd
--tmp-overlay
--uid
--unsetenv
--userns-block-fd

View File

@@ -13,7 +13,6 @@ if bash_completion_dir == ''
default_value: '',
pkgconfig: 'completionsdir',
pkgconfig_define: [
'prefix', get_option('prefix'),
'datadir', get_option('prefix') / get_option('datadir'),
],
)

View File

@@ -1,158 +0,0 @@
AC_PREREQ([2.63])
AC_INIT([bubblewrap], [0.8.0], [atomic-devel@projectatomic.io])
AC_CONFIG_HEADER([config.h])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux])
AC_USE_SYSTEM_EXTENSIONS
AM_INIT_AUTOMAKE([1.11 -Wno-portability foreign no-define tar-ustar no-dist-gzip dist-xz])
AM_MAINTAINER_MODE([enable])
AM_SILENT_RULES([yes])
AC_SYS_LARGEFILE
AC_PROG_CC
AM_PROG_CC_C_O
PKG_PROG_PKG_CONFIG([])
AC_CHECK_HEADERS([sys/capability.h], [], [AC_MSG_ERROR([*** POSIX caps headers not found])])
AC_ARG_ENABLE(man,
[AS_HELP_STRING([--enable-man],
[generate man pages [default=auto]])],,
enable_man=maybe)
AS_IF([test "$enable_man" != no], [
AC_PATH_PROG([XSLTPROC], [xsltproc], [])
AS_IF([test -z "$XSLTPROC"], [
AS_IF([test "$enable_man" = yes], [
AC_MSG_ERROR([xsltproc is required for --enable-man])
])
enable_man=no
], [
enable_man=yes
])
])
AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no)
AC_ARG_WITH([bash-completion-dir],
AS_HELP_STRING([--with-bash-completion-dir[=PATH]],
[Install the bash auto-completion script in this directory. @<:@default=yes@:>@]),
[],
[with_bash_completion_dir=yes])
AS_IF([test "x$with_bash_completion_dir" = "xyes"],
[
PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
[BASH_COMPLETION_DIR="`pkg-config --variable=completionsdir bash-completion`"],
[BASH_COMPLETION_DIR="$datadir/bash-completion/completions"])
],
[
BASH_COMPLETION_DIR="$with_bash_completion_dir"
])
AC_SUBST([BASH_COMPLETION_DIR])
AM_CONDITIONAL([ENABLE_BASH_COMPLETION],[test "x$with_bash_completion_dir" != "xno"])
AC_ARG_WITH([zsh-completion-dir],
AS_HELP_STRING([--with-zsh-completion-dir[=PATH]],
[Install the zsh auto-completion script in this directory. @<:@default=yes@:>@]),
[],
[with_zsh_completion_dir=yes])
AS_IF([test "x$with_zsh_completion_dir" = "xyes"],
[ZSH_COMPLETION_DIR="$datadir/zsh/site-functions"],
[ZSH_COMPLETION_DIR="$with_zsh_completion_dir"])
AC_SUBST([ZSH_COMPLETION_DIR])
AM_CONDITIONAL([ENABLE_ZSH_COMPLETION], [test "x$with_zsh_completion_dir" != "xno"])
# ------------------------------------------------------------------------------
have_selinux=no
AC_ARG_ENABLE(selinux, AS_HELP_STRING([--disable-selinux], [Disable optional SELINUX support]))
AS_IF([test "x$enable_selinux" != "xno"], [
PKG_CHECK_MODULES([SELINUX], [libselinux >= 2.1.9],
[AC_DEFINE(HAVE_SELINUX, 1, [Define if SELinux is available])
have_selinux=yes
M4_DEFINES="$M4_DEFINES -DHAVE_SELINUX"],
[have_selinux=no])
AS_IF([test "x$have_selinux" = xno && test "x$enable_selinux" = xyes],
[AC_MSG_ERROR([*** SELinux support requested but libraries not found])])
PKG_CHECK_MODULES([SELINUX_2_3], [libselinux >= 2.3],
[AC_DEFINE(HAVE_SELINUX_2_3, 1, [Define if SELinux is version >= 2.3])],
[:])
])
AM_CONDITIONAL(HAVE_SELINUX, [test "$have_selinux" = "yes"])
dnl Keep this in sync with ostree, except remove -Werror=declaration-after-statement
CC_CHECK_FLAGS_APPEND([WARN_CFLAGS], [CFLAGS], [\
-pipe \
-Wall \
-Werror=shadow \
-Werror=empty-body \
-Werror=strict-prototypes \
-Werror=missing-prototypes \
-Werror=implicit-function-declaration \
"-Werror=format=2 -Werror=format-security -Werror=format-nonliteral" \
-Werror=pointer-arith -Werror=init-self \
-Werror=missing-declarations \
-Werror=return-type \
-Werror=overflow \
-Werror=int-conversion \
-Werror=parenthesis \
-Werror=incompatible-pointer-types \
-Werror=misleading-indentation \
-Werror=missing-include-dirs -Werror=aggregate-return \
-Werror=switch-default \
-Wswitch-enum \
])
AC_SUBST(WARN_CFLAGS)
AC_CHECK_LIB(cap, cap_from_text)
AS_IF([test "$ac_cv_lib_cap_cap_from_text" != "yes"],
[AC_MSG_ERROR([*** libcap requested but not found])])
AC_ARG_WITH(priv-mode,
AS_HELP_STRING([--with-priv-mode=setuid/none],
[How to set privilege-raising during make install]),
[],
[with_priv_mode="none"])
AM_CONDITIONAL(PRIV_MODE_SETUID, test "x$with_priv_mode" = "xsetuid")
AC_ARG_ENABLE(sudo,
AS_HELP_STRING([--enable-sudo],[Use sudo to set privileged mode on binaries during install (only needed if --with-priv-mode used)]),
[SUDO_BIN="sudo"], [SUDO_BIN=""])
AC_SUBST([SUDO_BIN])
AC_ARG_ENABLE(require-userns,
AS_HELP_STRING([--enable-require-userns=yes/no (default no)],
[Require user namespaces by default when installed suid]),
[],
[enable_require_userns="no"])
AS_IF([ test "x$enable_require_userns" = "xyes" ], [
AC_DEFINE(ENABLE_REQUIRE_USERNS, 1, [Define if userns should be used by default in suid mode])
])
AC_PROG_AWK
AC_REQUIRE_AUX_FILE([tap-driver.sh])
AC_CONFIG_FILES([
Makefile
])
AC_OUTPUT
echo "
bubblewrap $VERSION
===================
man pages (xsltproc): $enable_man
SELinux: $have_selinux
setuid mode on make install: $with_priv_mode
require default userns: $enable_require_userns
mysteriously satisfying to pop: yes"
echo ""

348
git.mk
View File

@@ -1,348 +0,0 @@
# git.mk, a small Makefile to autogenerate .gitignore files
# for autotools-based projects.
#
# Copyright 2009, Red Hat, Inc.
# Copyright 2010,2011,2012,2013 Behdad Esfahbod
# Written by Behdad Esfahbod
#
# Copying and distribution of this file, with or without modification,
# is permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.
#
# The latest version of this file can be downloaded from:
GIT_MK_URL = https://raw.githubusercontent.com/behdad/git.mk/master/git.mk
#
# Bugs, etc, should be reported upstream at:
# https://github.com/behdad/git.mk
#
# To use in your project, import this file in your git repo's toplevel,
# then do "make -f git.mk". This modifies all Makefile.am files in
# your project to -include git.mk. Remember to add that line to new
# Makefile.am files you create in your project, or just rerun the
# "make -f git.mk".
#
# This enables automatic .gitignore generation. If you need to ignore
# more files, add them to the GITIGNOREFILES variable in your Makefile.am.
# But think twice before doing that. If a file has to be in .gitignore,
# chances are very high that it's a generated file and should be in one
# of MOSTLYCLEANFILES, CLEANFILES, DISTCLEANFILES, or MAINTAINERCLEANFILES.
#
# The only case that you need to manually add a file to GITIGNOREFILES is
# when remove files in one of mostlyclean-local, clean-local, distclean-local,
# or maintainer-clean-local make targets.
#
# Note that for files like editor backup, etc, there are better places to
# ignore them. See "man gitignore".
#
# If "make maintainer-clean" removes the files but they are not recognized
# by this script (that is, if "git status" shows untracked files still), send
# me the output of "git status" as well as your Makefile.am and Makefile for
# the directories involved and I'll diagnose.
#
# For a list of toplevel files that should be in MAINTAINERCLEANFILES, see
# Makefile.am.sample in the git.mk git repo.
#
# Don't EXTRA_DIST this file. It is supposed to only live in git clones,
# not tarballs. It serves no useful purpose in tarballs and clutters the
# build dir.
#
# This file knows how to handle autoconf, automake, libtool, gtk-doc,
# gnome-doc-utils, yelp.m4, mallard, intltool, gsettings, dejagnu, appdata,
# appstream.
#
# This makefile provides the following targets:
#
# - all: "make all" will build all gitignore files.
# - gitignore: makes all gitignore files in the current dir and subdirs.
# - .gitignore: make gitignore file for the current dir.
# - gitignore-recurse: makes all gitignore files in the subdirs.
#
# KNOWN ISSUES:
#
# - Recursive configure doesn't work as $(top_srcdir)/git.mk inside the
# submodule doesn't find us. If you have configure.{in,ac} files in
# subdirs, add a proxy git.mk file in those dirs that simply does:
# "include $(top_srcdir)/../git.mk". Add more ..'s to your taste.
# And add those files to git. See vte/gnome-pty-helper/git.mk for
# example.
#
###############################################################################
# Variables user modules may want to add to toplevel MAINTAINERCLEANFILES:
###############################################################################
#
# Most autotools-using modules should be fine including this variable in their
# toplevel MAINTAINERCLEANFILES:
GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL = \
$(srcdir)/aclocal.m4 \
$(srcdir)/autoscan.log \
$(srcdir)/configure.scan \
`AUX_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_AUX_DIR:$$1' ./configure.ac); \
test "x$$AUX_DIR" = "x$(srcdir)/" && AUX_DIR=$(srcdir); \
for x in \
ar-lib \
compile \
config.guess \
config.sub \
depcomp \
install-sh \
ltmain.sh \
missing \
mkinstalldirs \
test-driver \
ylwrap \
; do echo "$$AUX_DIR/$$x"; done` \
`cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_HEADERS:$$1' ./configure.ac | \
head -n 1 | while read f; do echo "$(srcdir)/$$f.in"; done`
#
# All modules should also be fine including the following variable, which
# removes automake-generated Makefile.in files:
GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN = \
`cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_FILES:$$1' ./configure.ac | \
while read f; do \
case $$f in Makefile|*/Makefile) \
test -f "$(srcdir)/$$f.am" && echo "$(srcdir)/$$f.in";; esac; \
done`
#
# Modules that use libtool and use AC_CONFIG_MACRO_DIR() may also include this,
# though it's harmless to include regardless.
GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL = \
`MACRO_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_MACRO_DIR:$$1' ./configure.ac); \
if test "x$$MACRO_DIR" != "x$(srcdir)/"; then \
for x in \
libtool.m4 \
ltoptions.m4 \
ltsugar.m4 \
ltversion.m4 \
lt~obsolete.m4 \
; do echo "$$MACRO_DIR/$$x"; done; \
fi`
###############################################################################
# Default rule is to install ourselves in all Makefile.am files:
###############################################################################
git-all: git-mk-install
git-mk-install:
@echo "Installing git makefile"
@any_failed=; \
find "`test -z "$(top_srcdir)" && echo . || echo "$(top_srcdir)"`" -name Makefile.am | while read x; do \
if grep 'include .*/git.mk' $$x >/dev/null; then \
echo "$$x already includes git.mk"; \
else \
failed=; \
echo "Updating $$x"; \
{ cat $$x; \
echo ''; \
echo '-include $$(top_srcdir)/git.mk'; \
} > $$x.tmp || failed=1; \
if test x$$failed = x; then \
mv $$x.tmp $$x || failed=1; \
fi; \
if test x$$failed = x; then : else \
echo "Failed updating $$x"; >&2 \
any_failed=1; \
fi; \
fi; done; test -z "$$any_failed"
git-mk-update:
wget $(GIT_MK_URL) -O $(top_srcdir)/git.mk
.PHONY: git-all git-mk-install git-mk-update
###############################################################################
# Actual .gitignore generation:
###############################################################################
$(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk
@echo "git.mk: Generating $@"
@{ \
if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \
for x in \
$(DOC_MODULE)-decl-list.txt \
$(DOC_MODULE)-decl.txt \
tmpl/$(DOC_MODULE)-unused.sgml \
"tmpl/*.bak" \
$(REPORT_FILES) \
$(DOC_MODULE).pdf \
xml html \
; do echo "/$$x"; done; \
FLAVOR=$$(cd $(top_srcdir); $(AUTOCONF) --trace 'GTK_DOC_CHECK:$$2' ./configure.ac); \
case $$FLAVOR in *no-tmpl*) echo /tmpl;; esac; \
if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-types"; then \
echo "/$(DOC_MODULE).types"; \
fi; \
if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-sections"; then \
echo "/$(DOC_MODULE)-sections.txt"; \
fi; \
if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
for x in \
$(SETUP_FILES) \
$(DOC_MODULE).types \
; do echo "/$$x"; done; \
fi; \
fi; \
if test "x$(DOC_MODULE)$(DOC_ID)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \
for lc in $(DOC_LINGUAS); do \
for x in \
$(if $(DOC_MODULE),$(DOC_MODULE).xml) \
$(DOC_PAGES) \
$(DOC_INCLUDES) \
; do echo "/$$lc/$$x"; done; \
done; \
for x in \
$(_DOC_OMF_ALL) \
$(_DOC_DSK_ALL) \
$(_DOC_HTML_ALL) \
$(_DOC_MOFILES) \
$(DOC_H_FILE) \
"*/.xml2po.mo" \
"*/*.omf.out" \
; do echo /$$x; done; \
fi; \
if test "x$(HELP_ID)" = x -o "x$(HELP_LINGUAS)" = x; then :; else \
for lc in $(HELP_LINGUAS); do \
for x in \
$(HELP_FILES) \
"$$lc.stamp" \
"$$lc.mo" \
; do echo "/$$lc/$$x"; done; \
done; \
fi; \
if test "x$(gsettings_SCHEMAS)" = x; then :; else \
for x in \
$(gsettings_SCHEMAS:.xml=.valid) \
$(gsettings__enum_file) \
; do echo "/$$x"; done; \
fi; \
if test "x$(appdata_XML)" = x; then :; else \
for x in \
$(appdata_XML:.xml=.valid) \
; do echo "/$$x"; done; \
fi; \
if test "x$(appstream_XML)" = x; then :; else \
for x in \
$(appstream_XML:.xml=.valid) \
; do echo "/$$x"; done; \
fi; \
if test -f $(srcdir)/po/Makefile.in.in; then \
for x in \
po/Makefile.in.in \
po/Makefile.in.in~ \
po/Makefile.in \
po/Makefile \
po/Makevars.template \
po/POTFILES \
po/Rules-quot \
po/stamp-it \
po/stamp-po \
po/.intltool-merge-cache \
"po/*.gmo" \
"po/*.header" \
"po/*.mo" \
"po/*.sed" \
"po/*.sin" \
po/$(GETTEXT_PACKAGE).pot \
intltool-extract.in \
intltool-merge.in \
intltool-update.in \
; do echo "/$$x"; done; \
fi; \
if test -f $(srcdir)/configure; then \
for x in \
autom4te.cache \
configure \
config.h \
stamp-h1 \
libtool \
config.lt \
; do echo "/$$x"; done; \
fi; \
if test "x$(DEJATOOL)" = x; then :; else \
for x in \
$(DEJATOOL) \
; do echo "/$$x.sum"; echo "/$$x.log"; done; \
echo /site.exp; \
fi; \
if test "x$(am__dirstamp)" = x; then :; else \
echo "$(am__dirstamp)"; \
fi; \
if test "x$(LTCOMPILE)" = x -a "x$(LTCXXCOMPILE)" = x -a "x$(GTKDOC_RUN)" = x; then :; else \
for x in \
"*.lo" \
".libs" "_libs" \
; do echo "$$x"; done; \
fi; \
for x in \
.gitignore \
$(GITIGNOREFILES) \
$(CLEANFILES) \
$(PROGRAMS) $(check_PROGRAMS) $(EXTRA_PROGRAMS) \
$(LIBRARIES) $(check_LIBRARIES) $(EXTRA_LIBRARIES) \
$(LTLIBRARIES) $(check_LTLIBRARIES) $(EXTRA_LTLIBRARIES) \
so_locations \
$(MOSTLYCLEANFILES) \
$(TEST_LOGS) \
$(TEST_LOGS:.log=.trs) \
$(TEST_SUITE_LOG) \
$(TESTS:=.test) \
"*.gcda" \
"*.gcno" \
$(DISTCLEANFILES) \
$(am__CONFIG_DISTCLEAN_FILES) \
$(CONFIG_CLEAN_FILES) \
TAGS ID GTAGS GRTAGS GSYMS GPATH tags \
"*.tab.c" \
$(MAINTAINERCLEANFILES) \
$(BUILT_SOURCES) \
$(patsubst %.vala,%.c,$(filter %.vala,$(SOURCES))) \
$(filter %_vala.stamp,$(DIST_COMMON)) \
$(filter %.vapi,$(DIST_COMMON)) \
$(filter $(addprefix %,$(notdir $(patsubst %.vapi,%.h,$(filter %.vapi,$(DIST_COMMON))))),$(DIST_COMMON)) \
Makefile \
Makefile.in \
"*.orig" \
"*.rej" \
"*.bak" \
"*~" \
".*.sw[nop]" \
".dirstamp" \
; do echo "/$$x"; done; \
for x in \
"*.$(OBJEXT)" \
$(DEPDIR) \
; do echo "$$x"; done; \
} | \
sed "s@^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \
sed 's@/[.]/@/@g' | \
LC_ALL=C sort | uniq > $@.tmp && \
mv $@.tmp $@;
all: $(srcdir)/.gitignore gitignore-recurse-maybe
gitignore: $(srcdir)/.gitignore gitignore-recurse
gitignore-recurse-maybe:
@for subdir in $(DIST_SUBDIRS); do \
case " $(SUBDIRS) " in \
*" $$subdir "*) :;; \
*) test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir");; \
esac; \
done
gitignore-recurse:
@for subdir in $(DIST_SUBDIRS); do \
test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir"); \
done
maintainer-clean: gitignore-clean
gitignore-clean:
-rm -f $(srcdir)/.gitignore
.PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe

View File

@@ -1,292 +0,0 @@
dnl Macros to check the presence of generic (non-typed) symbols.
dnl Copyright (c) 2006-2008 Diego Pettenò <flameeyes@gmail.com>
dnl Copyright (c) 2006-2008 xine project
dnl Copyright (c) 2012 Lucas De Marchi <lucas.de.marchi@gmail.com>
dnl
dnl This program is free software; you can redistribute it and/or modify
dnl it under the terms of the GNU General Public License as published by
dnl the Free Software Foundation; either version 2, or (at your option)
dnl any later version.
dnl
dnl This program is distributed in the hope that it will be useful,
dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
dnl GNU General Public License for more details.
dnl
dnl You should have received a copy of the GNU General Public License
dnl along with this program; if not, write to the Free Software
dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
dnl 02110-1301, USA.
dnl
dnl As a special exception, the copyright owners of the
dnl macro gives unlimited permission to copy, distribute and modify the
dnl configure scripts that are the output of Autoconf when processing the
dnl Macro. You need not follow the terms of the GNU General Public
dnl License when using or distributing such scripts, even though portions
dnl of the text of the Macro appear in them. The GNU General Public
dnl License (GPL) does govern all other use of the material that
dnl constitutes the Autoconf Macro.
dnl
dnl This special exception to the GPL applies to versions of the
dnl Autoconf Macro released by this project. When you make and
dnl distribute a modified version of the Autoconf Macro, you may extend
dnl this special exception to the GPL to apply to your modified version as
dnl well.
dnl Check if FLAG in ENV-VAR is supported by compiler and append it
dnl to WHERE-TO-APPEND variable. Note that we invert -Wno-* checks to
dnl -W* as gcc cannot test for negated warnings. If a C snippet is passed,
dnl use it, otherwise use a simple main() definition that just returns 0.
dnl CC_CHECK_FLAG_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG], [C-SNIPPET])
AC_DEFUN([CC_CHECK_FLAG_APPEND], [
AC_CACHE_CHECK([if $CC supports flag $3 in envvar $2],
AS_TR_SH([cc_cv_$2_$3]),
[eval "AS_TR_SH([cc_save_$2])='${$2}'"
eval "AS_TR_SH([$2])='${cc_save_$2} -Werror `echo "$3" | sed 's/^-Wno-/-W/'`'"
AC_LINK_IFELSE([AC_LANG_SOURCE(ifelse([$4], [],
[int main(void) { return 0; } ],
[$4]))],
[eval "AS_TR_SH([cc_cv_$2_$3])='yes'"],
[eval "AS_TR_SH([cc_cv_$2_$3])='no'"])
eval "AS_TR_SH([$2])='$cc_save_$2'"])
AS_IF([eval test x$]AS_TR_SH([cc_cv_$2_$3])[ = xyes],
[eval "$1='${$1} $3'"])
])
dnl CC_CHECK_FLAGS_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG1 FLAG2], [C-SNIPPET])
AC_DEFUN([CC_CHECK_FLAGS_APPEND], [
for flag in [$3]; do
CC_CHECK_FLAG_APPEND([$1], [$2], $flag, [$4])
done
])
dnl Check if the flag is supported by linker (cacheable)
dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
AC_DEFUN([CC_CHECK_LDFLAGS], [
AC_CACHE_CHECK([if $CC supports $1 flag],
AS_TR_SH([cc_cv_ldflags_$1]),
[ac_save_LDFLAGS="$LDFLAGS"
LDFLAGS="$LDFLAGS $1"
AC_LINK_IFELSE([int main() { return 1; }],
[eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"],
[eval "AS_TR_SH([cc_cv_ldflags_$1])="])
LDFLAGS="$ac_save_LDFLAGS"
])
AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes],
[$2], [$3])
])
dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for
dnl the current linker to avoid undefined references in a shared object.
AC_DEFUN([CC_NOUNDEFINED], [
dnl We check $host for which systems to enable this for.
AC_REQUIRE([AC_CANONICAL_HOST])
case $host in
dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads
dnl are requested, as different implementations are present; to avoid problems
dnl use -Wl,-z,defs only for those platform not behaving this way.
*-freebsd* | *-openbsd*) ;;
*)
dnl First of all check for the --no-undefined variant of GNU ld. This allows
dnl for a much more readable command line, so that people can understand what
dnl it does without going to look for what the heck -z defs does.
for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do
CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"])
break
done
;;
esac
AC_SUBST([LDFLAGS_NOUNDEFINED])
])
dnl Check for a -Werror flag or equivalent. -Werror is the GCC
dnl and ICC flag that tells the compiler to treat all the warnings
dnl as fatal. We usually need this option to make sure that some
dnl constructs (like attributes) are not simply ignored.
dnl
dnl Other compilers don't support -Werror per se, but they support
dnl an equivalent flag:
dnl - Sun Studio compiler supports -errwarn=%all
AC_DEFUN([CC_CHECK_WERROR], [
AC_CACHE_CHECK(
[for $CC way to treat warnings as errors],
[cc_cv_werror],
[CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror],
[CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])])
])
])
AC_DEFUN([CC_CHECK_ATTRIBUTE], [
AC_REQUIRE([CC_CHECK_WERROR])
AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))],
AS_TR_SH([cc_cv_attribute_$1]),
[ac_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $cc_cv_werror"
AC_COMPILE_IFELSE([AC_LANG_SOURCE([$3])],
[eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"],
[eval "AS_TR_SH([cc_cv_attribute_$1])='no'"])
CFLAGS="$ac_save_CFLAGS"
])
AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes],
[AC_DEFINE(
AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1,
[Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))]
)
$4],
[$5])
])
AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [
CC_CHECK_ATTRIBUTE(
[constructor],,
[void __attribute__((constructor)) ctor() { int a; }],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_FORMAT], [
CC_CHECK_ATTRIBUTE(
[format], [format(printf, n, n)],
[void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [
CC_CHECK_ATTRIBUTE(
[format_arg], [format_arg(printf)],
[char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [
CC_CHECK_ATTRIBUTE(
[visibility_$1], [visibility("$1")],
[void __attribute__((visibility("$1"))) $1_function() { }],
[$2], [$3])
])
AC_DEFUN([CC_ATTRIBUTE_NONNULL], [
CC_CHECK_ATTRIBUTE(
[nonnull], [nonnull()],
[void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_UNUSED], [
CC_CHECK_ATTRIBUTE(
[unused], ,
[void some_function(void *foo, __attribute__((unused)) void *bar);],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [
CC_CHECK_ATTRIBUTE(
[sentinel], ,
[void some_function(void *foo, ...) __attribute__((sentinel));],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [
CC_CHECK_ATTRIBUTE(
[deprecated], ,
[void some_function(void *foo, ...) __attribute__((deprecated));],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_ALIAS], [
CC_CHECK_ATTRIBUTE(
[alias], [weak, alias],
[void other_function(void *foo) { }
void some_function(void *foo) __attribute__((weak, alias("other_function")));],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_MALLOC], [
CC_CHECK_ATTRIBUTE(
[malloc], ,
[void * __attribute__((malloc)) my_alloc(int n);],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_PACKED], [
CC_CHECK_ATTRIBUTE(
[packed], ,
[struct astructure { char a; int b; long c; void *d; } __attribute__((packed));],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_CONST], [
CC_CHECK_ATTRIBUTE(
[const], ,
[int __attribute__((const)) twopow(int n) { return 1 << n; } ],
[$1], [$2])
])
AC_DEFUN([CC_FLAG_VISIBILITY], [
AC_REQUIRE([CC_CHECK_WERROR])
AC_CACHE_CHECK([if $CC supports -fvisibility=hidden],
[cc_cv_flag_visibility],
[cc_flag_visibility_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $cc_cv_werror"
CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden],
cc_cv_flag_visibility='yes',
cc_cv_flag_visibility='no')
CFLAGS="$cc_flag_visibility_save_CFLAGS"])
AS_IF([test "x$cc_cv_flag_visibility" = "xyes"],
[AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1,
[Define this if the compiler supports the -fvisibility flag])
$1],
[$2])
])
AC_DEFUN([CC_FUNC_EXPECT], [
AC_REQUIRE([CC_CHECK_WERROR])
AC_CACHE_CHECK([if compiler has __builtin_expect function],
[cc_cv_func_expect],
[ac_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $cc_cv_werror"
AC_COMPILE_IFELSE([AC_LANG_SOURCE(
[int some_function() {
int a = 3;
return (int)__builtin_expect(a, 3);
}])],
[cc_cv_func_expect=yes],
[cc_cv_func_expect=no])
CFLAGS="$ac_save_CFLAGS"
])
AS_IF([test "x$cc_cv_func_expect" = "xyes"],
[AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1,
[Define this if the compiler supports __builtin_expect() function])
$1],
[$2])
])
AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [
AC_REQUIRE([CC_CHECK_WERROR])
AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported],
[cc_cv_attribute_aligned],
[ac_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $cc_cv_werror"
for cc_attribute_align_try in 64 32 16 8 4 2; do
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
int main() {
static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0;
return c;
}])], [cc_cv_attribute_aligned=$cc_attribute_align_try; break])
done
CFLAGS="$ac_save_CFLAGS"
])
if test "x$cc_cv_attribute_aligned" != "x"; then
AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned],
[Define the highest alignment supported])
fi
])

View File

@@ -1,7 +1,7 @@
project(
'bubblewrap',
'c',
version : '0.8.0',
version : '0.11.0',
meson_version : '>=0.49.0',
default_options : [
'warning_level=2',

View File

@@ -45,7 +45,7 @@ option(
'require_userns',
type : 'boolean',
description : 'require user namespaces by default when installed setuid',
value : 'false',
value : false,
)
option(
'selinux',
@@ -57,7 +57,7 @@ option(
'tests',
type : 'boolean',
description : 'build tests',
value : 'true',
value : true,
)
option(
'zsh_completion',

View File

@@ -53,8 +53,8 @@ rtnl_send_request (int rtnl_fd,
struct sockaddr_nl dst_addr = { AF_NETLINK, 0 };
ssize_t sent;
sent = sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0,
(struct sockaddr *) &dst_addr, sizeof (dst_addr));
sent = TEMP_FAILURE_RETRY (sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0,
(struct sockaddr *) &dst_addr, sizeof (dst_addr)));
if (sent < 0)
return -1;
@@ -71,7 +71,7 @@ rtnl_read_reply (int rtnl_fd,
while (1)
{
received = recv (rtnl_fd, buffer, sizeof (buffer), 0);
received = TEMP_FAILURE_RETRY (recv (rtnl_fd, buffer, sizeof (buffer), 0));
if (received < 0)
return -1;

View File

@@ -1,18 +1,18 @@
bubblewrap release checklist
============================
* Collect release notes
* Update version number in `configure.ac` **and** `meson.build`
* Collect release notes in `NEWS`
* Update version number in `meson.build` and release date in `NEWS`
* Commit the changes
* `make distcheck`
* `meson dist -C ${builddir}`
* Do any final smoke-testing, e.g. update a package, install and test it
* `git evtag sign v$VERSION`
* Include the release notes in the tag message
* Include the release notes from `NEWS` in the tag message
* `git push --atomic origin main v$VERSION`
* https://github.com/containers/bubblewrap/releases/new
* Fill in the new version's tag in the "Tag version" box
* Title: `$VERSION`
* Copy the release notes into the description
* Upload the tarball that you built with `make distcheck`
* Upload the tarball that you built with `meson dist`
* Get the `sha256sum` of the tarball and append it to the description
* `Publish release`

View File

@@ -76,6 +76,21 @@ for ALT in "" "--unshare-user-try" "--unshare-pid" "--unshare-user-try --unshare
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"
@@ -94,7 +109,7 @@ 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 --format "%n %i" /proc/self/ns/*' 42>info.json 43>json-status.json 2>err.txt)
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
@@ -550,4 +565,128 @@ $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

View File

@@ -170,15 +170,15 @@ test_has_path_prefix (void)
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 },
{ "/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 },
{ "foo/bar", "/foo", true },
{ "/foo/bar", "foo", true },
};
size_t i;
@@ -200,6 +200,37 @@ test_has_path_prefix (void)
}
}
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)
@@ -210,6 +241,7 @@ main (int argc UNUSED,
test_strconcat3 ();
test_has_prefix ();
test_has_path_prefix ();
test_string_builder ();
printf ("1..%u\n", test_number);
return 0;
}

271
utils.c
View File

@@ -19,8 +19,11 @@
#include "config.h"
#include "utils.h"
#include <limits.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <sys/socket.h>
#include <sys/param.h>
#ifdef HAVE_SELINUX
#include <selinux/selinux.h>
#endif
@@ -32,21 +35,34 @@
#define security_check_context(x) security_check_context ((security_context_t) x)
#endif
__attribute__((format(printf, 1, 0))) static void
warnv (const char *format, va_list args)
bool bwrap_level_prefix = false;
__attribute__((format(printf, 2, 0))) static void
bwrap_logv (int severity,
const char *format,
va_list args,
const char *detail)
{
if (bwrap_level_prefix)
fprintf (stderr, "<%d>", severity);
fprintf (stderr, "bwrap: ");
vfprintf (stderr, format, args);
if (detail != NULL)
fprintf (stderr, ": %s", detail);
fprintf (stderr, "\n");
}
void
warn (const char *format, ...)
bwrap_log (int severity,
const char *format, ...)
{
va_list args;
va_start (args, format);
warnv (format, args);
bwrap_logv (severity, format, args, NULL);
va_end (args);
}
@@ -58,13 +74,24 @@ die_with_error (const char *format, ...)
errsv = errno;
fprintf (stderr, "bwrap: ");
va_start (args, format);
vfprintf (stderr, format, args);
bwrap_logv (LOG_ERR, format, args, strerror (errsv));
va_end (args);
fprintf (stderr, ": %s\n", strerror (errsv));
exit (1);
}
void
die_with_mount_error (const char *format, ...)
{
va_list args;
int errsv;
errsv = errno;
va_start (args, format);
bwrap_logv (LOG_ERR, format, args, mount_strerror (errsv));
va_end (args);
exit (1);
}
@@ -75,7 +102,7 @@ die (const char *format, ...)
va_list args;
va_start (args, format);
warnv (format, args);
bwrap_logv (LOG_ERR, format, args, NULL);
va_end (args);
exit (1);
@@ -126,9 +153,9 @@ xmalloc (size_t size)
}
void *
xcalloc (size_t size)
xcalloc (size_t nmemb, size_t size)
{
void *res = calloc (1, size);
void *res = calloc (nmemb, size);
if (res == NULL)
die_oom ();
@@ -138,9 +165,13 @@ xcalloc (size_t size)
void *
xrealloc (void *ptr, size_t size)
{
void *res = realloc (ptr, size);
void *res;
if (size != 0 && res == NULL)
assert (size != 0);
res = realloc (ptr, size);
if (res == NULL)
die_oom ();
return res;
}
@@ -183,7 +214,7 @@ bool
has_path_prefix (const char *str,
const char *prefix)
{
while (TRUE)
while (true)
{
/* Skip consecutive slashes to reach next path
element */
@@ -194,13 +225,13 @@ has_path_prefix (const char *str,
/* No more prefix path elements? Done! */
if (*prefix == 0)
return TRUE;
return true;
/* Compare path element */
while (*prefix != 0 && *prefix != '/')
{
if (*str != *prefix)
return FALSE;
return false;
str++;
prefix++;
}
@@ -208,7 +239,7 @@ has_path_prefix (const char *str,
/* Matched prefix path element,
must be entire str path element */
if (*str != '/' && *str != 0)
return FALSE;
return false;
}
}
@@ -216,7 +247,7 @@ bool
path_equal (const char *path1,
const char *path2)
{
while (TRUE)
while (true)
{
/* Skip consecutive slashes to reach next path
element */
@@ -233,14 +264,14 @@ path_equal (const char *path1,
while (*path1 != 0 && *path1 != '/')
{
if (*path1 != *path2)
return FALSE;
return false;
path1++;
path2++;
}
/* Matched path1 path element, must be entire path element */
if (*path2 != '/' && *path2 != 0)
return FALSE;
return false;
}
}
@@ -347,7 +378,7 @@ fdwalk (int proc_fd, int (*cb)(void *data,
int res = 0;
DIR *d;
dfd = openat (proc_fd, "self/fd", O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY);
dfd = TEMP_FAILURE_RETRY (openat (proc_fd, "self/fd", O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY));
if (dfd == -1)
return res;
@@ -421,7 +452,7 @@ write_to_fd (int fd,
/* Sets errno on error (!= 0), ENOSPC on short write */
int
write_file_at (int dirfd,
write_file_at (int dfd,
const char *path,
const char *content)
{
@@ -429,7 +460,7 @@ write_file_at (int dirfd,
bool res;
int errsv;
fd = openat (dirfd, path, O_RDWR | O_CLOEXEC, 0);
fd = TEMP_FAILURE_RETRY (openat (dfd, path, O_RDWR | O_CLOEXEC, 0));
if (fd == -1)
return -1;
@@ -454,7 +485,7 @@ create_file (const char *path,
int res;
int errsv;
fd = creat (path, mode);
fd = TEMP_FAILURE_RETRY (creat (path, mode));
if (fd == -1)
return -1;
@@ -503,7 +534,7 @@ copy_file_data (int sfd,
char buffer[BUFSIZE];
ssize_t bytes_read;
while (TRUE)
while (true)
{
bytes_read = read (sfd, buffer, BUFSIZE);
if (bytes_read == -1)
@@ -535,11 +566,11 @@ copy_file (const char *src_path,
int res;
int errsv;
sfd = open (src_path, O_CLOEXEC | O_RDONLY);
sfd = TEMP_FAILURE_RETRY (open (src_path, O_CLOEXEC | O_RDONLY));
if (sfd == -1)
return -1;
dfd = creat (dst_path, mode);
dfd = TEMP_FAILURE_RETRY (creat (dst_path, mode));
if (dfd == -1)
{
errsv = errno;
@@ -577,6 +608,12 @@ load_file_data (int fd,
{
if (data_len == data_read + 1)
{
if (data_len > SSIZE_MAX / 2)
{
errno = EFBIG;
return NULL;
}
data_len *= 2;
data = xrealloc (data, data_len);
}
@@ -603,14 +640,14 @@ load_file_data (int fd,
/* Sets errno on error (== NULL),
* Always ensures terminating zero */
char *
load_file_at (int dirfd,
load_file_at (int dfd,
const char *path)
{
int fd;
char *data;
int errsv;
fd = openat (dirfd, path, O_CLOEXEC | O_RDONLY);
fd = TEMP_FAILURE_RETRY (openat (dfd, path, O_CLOEXEC | O_RDONLY));
if (fd == -1)
return NULL;
@@ -716,15 +753,15 @@ mkdir_with_parents (const char *pathname,
read back with read_pid_from_socket(), and then the kernel has
translated it between namespaces as needed. */
void
send_pid_on_socket (int socket)
send_pid_on_socket (int sockfd)
{
char buf[1] = { 0 };
struct msghdr msg = {};
struct iovec iov = { buf, sizeof (buf) };
const ssize_t control_len_snd = CMSG_SPACE(sizeof(struct ucred));
char control_buf_snd[control_len_snd];
_Alignas(struct cmsghdr) char control_buf_snd[control_len_snd];
struct cmsghdr *cmsg;
struct ucred *cred;
struct ucred cred;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
@@ -735,13 +772,13 @@ send_pid_on_socket (int socket)
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_CREDENTIALS;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
cred = (struct ucred *)CMSG_DATA(cmsg);
cred->pid = getpid ();
cred->uid = geteuid ();
cred->gid = getegid ();
cred.pid = getpid ();
cred.uid = geteuid ();
cred.gid = getegid ();
memcpy (CMSG_DATA (cmsg), &cred, sizeof (cred));
if (sendmsg (socket, &msg, 0) < 0)
if (TEMP_FAILURE_RETRY (sendmsg (sockfd, &msg, 0)) < 0)
die_with_error ("Can't send pid");
}
@@ -758,13 +795,13 @@ create_pid_socketpair (int sockets[2])
}
int
read_pid_from_socket (int socket)
read_pid_from_socket (int sockfd)
{
char recv_buf[1] = { 0 };
struct msghdr msg = {};
struct iovec iov = { recv_buf, sizeof (recv_buf) };
const ssize_t control_len_rcv = CMSG_SPACE(sizeof(struct ucred));
char control_buf_rcv[control_len_rcv];
_Alignas(struct cmsghdr) char control_buf_rcv[control_len_rcv];
struct cmsghdr* cmsg;
msg.msg_iov = &iov;
@@ -772,7 +809,7 @@ read_pid_from_socket (int socket)
msg.msg_control = control_buf_rcv;
msg.msg_controllen = control_len_rcv;
if (recvmsg (socket, &msg, 0) < 0)
if (TEMP_FAILURE_RETRY (recvmsg (sockfd, &msg, 0)) < 0)
die_with_error ("Can't read pid from socket");
if (msg.msg_controllen <= 0)
@@ -785,8 +822,10 @@ read_pid_from_socket (int socket)
cmsg->cmsg_type == SCM_CREDENTIALS &&
payload_len == sizeof(struct ucred))
{
struct ucred *cred = (struct ucred *)CMSG_DATA(cmsg);
return cred->pid;
struct ucred cred;
memcpy (&cred, CMSG_DATA (cmsg), sizeof (cred));
return cred.pid;
}
}
die ("No pid returned on socket");
@@ -803,6 +842,8 @@ readlink_malloc (const char *pathname)
do
{
if (size > SIZE_MAX / 2)
die ("Symbolic link target pathname too long");
size *= 2;
value = xrealloc (value, size);
n = readlink (pathname, value, size - 1);
@@ -891,3 +932,149 @@ label_exec (UNUSED const char *exec_label)
#endif
return 0;
}
/*
* Like strerror(), but specialized for a failed mount(2) call.
*/
const char *
mount_strerror (int errsv)
{
switch (errsv)
{
case ENOSPC:
/* "No space left on device" misleads users into thinking there
* is some sort of disk-space problem, but mount(2) uses that
* errno value to mean something more like "limit exceeded". */
return ("Limit exceeded (ENOSPC). "
"(Hint: Check that /proc/sys/fs/mount-max is sufficient, "
"typically 100000)");
default:
return strerror (errsv);
}
}
/*
* Return a + b if it would not overflow.
* Die with an "out of memory" error if it would.
*/
static size_t
xadd (size_t a, size_t b)
{
#if defined(__GNUC__) && __GNUC__ >= 5
size_t result;
if (__builtin_add_overflow (a, b, &result))
die_oom ();
return result;
#else
if (a > SIZE_MAX - b)
die_oom ();
return a + b;
#endif
}
/*
* Return a * b if it would not overflow.
* Die with an "out of memory" error if it would.
*/
static size_t
xmul (size_t a, size_t b)
{
#if defined(__GNUC__) && __GNUC__ >= 5
size_t result;
if (__builtin_mul_overflow (a, b, &result))
die_oom ();
return result;
#else
if (b != 0 && a > SIZE_MAX / b)
die_oom ();
return a * b;
#endif
}
void
strappend (StringBuilder *dest, const char *src)
{
size_t len = strlen (src);
size_t new_offset = xadd (dest->offset, len);
if (new_offset >= dest->size)
{
dest->size = xmul (xadd (new_offset, 1), 2);
dest->str = xrealloc (dest->str, dest->size);
}
/* Preserves the invariant that dest->str is always null-terminated, even
* though the offset is positioned at the null byte for the next write.
*/
strncpy (dest->str + dest->offset, src, len + 1);
dest->offset = new_offset;
}
__attribute__((format (printf, 2, 3)))
void
strappendf (StringBuilder *dest, const char *fmt, ...)
{
va_list args;
int len;
size_t new_offset;
va_start (args, fmt);
len = vsnprintf (dest->str + dest->offset, dest->size - dest->offset, fmt, args);
va_end (args);
if (len < 0)
die_with_error ("vsnprintf");
new_offset = xadd (dest->offset, len);
if (new_offset >= dest->size)
{
dest->size = xmul (xadd (new_offset, 1), 2);
dest->str = xrealloc (dest->str, dest->size);
va_start (args, fmt);
len = vsnprintf (dest->str + dest->offset, dest->size - dest->offset, fmt, args);
va_end (args);
if (len < 0)
die_with_error ("vsnprintf");
}
dest->offset = new_offset;
}
void
strappend_escape_for_mount_options (StringBuilder *dest, const char *src)
{
bool unescaped = true;
for (;;)
{
if (dest->offset == dest->size)
{
dest->size = MAX (64, xmul (dest->size, 2));
dest->str = xrealloc (dest->str, dest->size);
}
switch (*src)
{
case '\0':
dest->str[dest->offset] = '\0';
return;
case '\\':
case ',':
case ':':
if (unescaped)
{
dest->str[dest->offset++] = '\\';
unescaped = false;
continue;
}
/* else fall through */
default:
dest->str[dest->offset++] = *src;
unescaped = true;
break;
}
src++;
}
}

49
utils.h
View File

@@ -24,26 +24,33 @@
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#if 0
#define __debug__(x) printf x
#define debug(...) bwrap_log (LOG_DEBUG, __VA_ARGS__)
#else
#define __debug__(x)
#define debug(...)
#endif
#define UNUSED __attribute__((__unused__))
#define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0]))
#define TRUE 1
#define FALSE 0
typedef int bool;
#ifndef TEMP_FAILURE_RETRY
#define TEMP_FAILURE_RETRY(expression) \
(__extension__ \
({ long int __result; \
do __result = (long int) (expression); \
while (__result == -1L && errno == EINTR); \
__result; }))
#endif
#define PIPE_READ_END 0
#define PIPE_WRITE_END 1
@@ -52,10 +59,17 @@ typedef int bool;
#define PR_SET_CHILD_SUBREAPER 36
#endif
void warn (const char *format,
...) __attribute__((format (printf, 1, 2)));
extern bool bwrap_level_prefix;
void bwrap_log (int severity,
const char *format,
...) __attribute__((format (printf, 2, 3)));
#define warn(...) bwrap_log (LOG_WARNING, __VA_ARGS__)
void die_with_error (const char *format,
...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2)));
void die_with_mount_error (const char *format,
...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2)));
void die (const char *format,
...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2)));
void die_oom (void) __attribute__((__noreturn__));
@@ -64,7 +78,7 @@ void die_unless_label_valid (const char *label);
void fork_intermediate_child (void);
void *xmalloc (size_t size);
void *xcalloc (size_t size);
void *xcalloc (size_t nmemb, size_t size);
void *xrealloc (void *ptr,
size_t size);
char *xstrdup (const char *str);
@@ -134,6 +148,8 @@ char *label_mount (const char *opt,
int label_exec (const char *exec_label);
int label_create_file (const char *file_label);
const char *mount_strerror (int errsv);
static inline void
cleanup_freep (void *p)
{
@@ -182,3 +198,20 @@ steal_pointer (void *pp)
/* type safety */
#define steal_pointer(pp) \
(0 ? (*(pp)) : (steal_pointer) (pp))
typedef struct _StringBuilder StringBuilder;
struct _StringBuilder
{
char * str;
size_t size;
size_t offset;
};
void strappend (StringBuilder *dest,
const char *src);
void strappendf (StringBuilder *dest,
const char *fmt,
...);
void strappend_escape_for_mount_options (StringBuilder *dest,
const char *src);