├── LICENSE
├── tests
├── use-as-subproject
│ ├── .gitignore
│ ├── config.h
│ ├── dummy-config.h.in
│ ├── README
│ ├── meson.build
│ └── assert-correct-rpath.py
├── test-specifying-userns.sh
├── test-specifying-pidns.sh
├── meson.build
├── libtest.sh
├── try-syscall.c
├── libtest-core.sh
├── test-utils.c
├── test-seccomp.py
└── test-run.sh
├── .dir-locals.el
├── bubblewrap.jpg
├── demos
├── flatpak.bpf
├── userns-block-fd.py
├── bubblewrap-shell.sh
└── flatpak-run.sh
├── uncrustify.sh
├── .editorconfig
├── completions
├── meson.build
├── zsh
│ ├── meson.build
│ └── _bwrap
└── bash
│ ├── meson.build
│ └── bwrap
├── CODE-OF-CONDUCT.md
├── Makefile-bwrap.am
├── autogen.sh
├── Makefile-docs.am
├── release-checklist.md
├── network.h
├── packaging
└── bubblewrap.spec
├── meson_options.txt
├── bind-mount.h
├── SECURITY.md
├── ci
└── builddeps.sh
├── Makefile.am
├── uncrustify.cfg
├── meson.build
├── .github
└── workflows
│ └── check.yml
├── configure.ac
├── network.c
├── utils.h
├── m4
└── attributes.m4
├── README.md
├── git.mk
├── bind-mount.c
└── utils.c
/LICENSE:
--------------------------------------------------------------------------------
1 | COPYING
--------------------------------------------------------------------------------
/tests/use-as-subproject/.gitignore:
--------------------------------------------------------------------------------
1 | /_build/
2 | /subprojects/
3 |
--------------------------------------------------------------------------------
/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ((c-mode . ((indent-tabs-mode . nil) (c-file-style . "gnu"))))
2 |
--------------------------------------------------------------------------------
/bubblewrap.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aperezdc/bubblewrap/main/bubblewrap.jpg
--------------------------------------------------------------------------------
/demos/flatpak.bpf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aperezdc/bubblewrap/main/demos/flatpak.bpf
--------------------------------------------------------------------------------
/tests/use-as-subproject/config.h:
--------------------------------------------------------------------------------
1 | #error Should not use superproject config.h to compile bubblewrap
2 |
--------------------------------------------------------------------------------
/tests/use-as-subproject/dummy-config.h.in:
--------------------------------------------------------------------------------
1 | #error Should not use superproject generated config.h to compile bubblewrap
2 |
--------------------------------------------------------------------------------
/uncrustify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | uncrustify -c uncrustify.cfg --no-backup `git ls-tree --name-only -r HEAD | grep \\\.[ch]$`
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.[ch]]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | indent_brace_style = gnu
6 |
--------------------------------------------------------------------------------
/completions/meson.build:
--------------------------------------------------------------------------------
1 | if get_option('bash_completion').enabled()
2 | subdir('bash')
3 | endif
4 |
5 | if get_option('zsh_completion').enabled()
6 | subdir('zsh')
7 | endif
8 |
--------------------------------------------------------------------------------
/CODE-OF-CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## The bubblewrap Project Community Code of Conduct
2 |
3 | The bubblewrap project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/HEAD/CODE-OF-CONDUCT.md).
4 |
--------------------------------------------------------------------------------
/tests/use-as-subproject/README:
--------------------------------------------------------------------------------
1 | This is a simple example of a project that uses bubblewrap as a
2 | subproject. The intention is that if this project can successfully build
3 | bubblewrap as a subproject, then so could Flatpak.
4 |
--------------------------------------------------------------------------------
/completions/zsh/meson.build:
--------------------------------------------------------------------------------
1 | zsh_completion_dir = get_option('zsh_completion_dir')
2 |
3 | if zsh_completion_dir == ''
4 | zsh_completion_dir = get_option('datadir') / 'zsh' / 'site-functions'
5 | endif
6 |
7 | install_data('_bwrap', install_dir : zsh_completion_dir)
8 |
--------------------------------------------------------------------------------
/Makefile-bwrap.am:
--------------------------------------------------------------------------------
1 | bwrap_SOURCES = \
2 | $(bwrap_srcpath)/bubblewrap.c \
3 | $(bwrap_srcpath)/bind-mount.h \
4 | $(bwrap_srcpath)/bind-mount.c \
5 | $(bwrap_srcpath)/network.h \
6 | $(bwrap_srcpath)/network.c \
7 | $(bwrap_srcpath)/utils.h \
8 | $(bwrap_srcpath)/utils.c \
9 | $(NULL)
10 |
11 | bwrap_CFLAGS = $(AM_CFLAGS)
12 | bwrap_LDADD = $(SELINUX_LIBS)
13 |
--------------------------------------------------------------------------------
/autogen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test -n "$srcdir" || srcdir=$(dirname "$0")
4 | test -n "$srcdir" || srcdir=.
5 |
6 | olddir=$(pwd)
7 | cd "$srcdir"
8 |
9 | if ! (autoreconf --version >/dev/null 2>&1); then
10 | echo "*** No autoreconf found, please install it ***"
11 | exit 1
12 | fi
13 |
14 | mkdir -p m4
15 |
16 | autoreconf --force --install --verbose
17 |
18 | cd "$olddir"
19 | test -n "$NOCONFIGURE" || "$srcdir/configure" "$@"
20 |
--------------------------------------------------------------------------------
/tests/use-as-subproject/meson.build:
--------------------------------------------------------------------------------
1 | project(
2 | 'use-bubblewrap-as-subproject',
3 | 'c',
4 | version : '0',
5 | meson_version : '>=0.49.0',
6 | )
7 |
8 | configure_file(
9 | output : 'config.h',
10 | input : 'dummy-config.h.in',
11 | configuration : configuration_data(),
12 | )
13 |
14 | subproject(
15 | 'bubblewrap',
16 | default_options : [
17 | 'install_rpath=${ORIGIN}/../lib',
18 | 'program_prefix=not-flatpak-',
19 | ],
20 | )
21 |
--------------------------------------------------------------------------------
/Makefile-docs.am:
--------------------------------------------------------------------------------
1 | XSLTPROC = xsltproc
2 |
3 | XSLTPROC_FLAGS = \
4 | --nonet \
5 | --stringparam man.output.quietly 1 \
6 | --stringparam funcsynopsis.style ansi \
7 | --stringparam man.th.extra1.suppress 1 \
8 | --stringparam man.authors.section.enabled 0 \
9 | --stringparam man.copyright.section.enabled 0
10 |
11 | .xml.1:
12 | $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
13 |
14 | if ENABLE_MAN
15 | man_MANS = bwrap.1
16 | CLEANFILES += $(man_MANS)
17 | endif
18 | EXTRA_DIST += bwrap.xml
19 |
--------------------------------------------------------------------------------
/release-checklist.md:
--------------------------------------------------------------------------------
1 | bubblewrap release checklist
2 | ============================
3 |
4 | * Collect release notes
5 | * Update version number in `configure.ac` **and** `meson.build`
6 | * Commit the changes
7 | * `meson dist -C ${builddir}`
8 | * Do any final smoke-testing, e.g. update a package, install and test it
9 | * `git evtag sign v$VERSION`
10 | * Include the release notes in the tag message
11 | * `git push --atomic origin main v$VERSION`
12 | * https://github.com/containers/bubblewrap/releases/new
13 | * Fill in the new version's tag in the "Tag version" box
14 | * Title: `$VERSION`
15 | * Copy the release notes into the description
16 | * Upload the tarball that you built with `meson dist`
17 | * Get the `sha256sum` of the tarball and append it to the description
18 | * `Publish release`
19 |
--------------------------------------------------------------------------------
/tests/use-as-subproject/assert-correct-rpath.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # Copyright 2022 Collabora Ltd.
3 | # SPDX-License-Identifier: LGPL-2.0-or-later
4 |
5 | import subprocess
6 | import sys
7 |
8 | if __name__ == '__main__':
9 | completed = subprocess.run(
10 | ['objdump', '-T', '-x', sys.argv[1]],
11 | stdout=subprocess.PIPE,
12 | )
13 | stdout = completed.stdout
14 | assert stdout is not None
15 | seen_rpath = False
16 |
17 | for line in stdout.splitlines():
18 | words = line.strip().split()
19 |
20 | if words and words[0] in (b'RPATH', b'RUNPATH'):
21 | print(line.decode(errors='backslashreplace'))
22 | assert len(words) == 2, words
23 | assert words[1] == b'${ORIGIN}/../lib', words
24 | seen_rpath = True
25 |
26 | assert seen_rpath
27 |
--------------------------------------------------------------------------------
/tests/test-specifying-userns.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -xeuo pipefail
4 |
5 | srcd=$(cd $(dirname "$0") && pwd)
6 | . "${srcd}/libtest.sh"
7 |
8 | echo "1..1"
9 |
10 | # This test needs user namespaces
11 | if test -n "${bwrap_is_suid:-}"; then
12 | echo "ok - # SKIP no setuid support for --unshare-user"
13 | else
14 | mkfifo donepipe
15 |
16 | $RUN --info-fd 42 --unshare-user sh -c 'readlink /proc/self/ns/user > sandbox-userns; cat < donepipe' >/dev/null 42>info.json &
17 | while ! test -f sandbox-userns; do sleep 1; done
18 | SANDBOX1PID=$(extract_child_pid info.json)
19 |
20 | $RUN --userns 11 readlink /proc/self/ns/user > sandbox2-userns 11< /proc/$SANDBOX1PID/ns/user
21 | echo foo > donepipe
22 |
23 | assert_files_equal sandbox-userns sandbox2-userns
24 |
25 | rm donepipe info.json sandbox-userns
26 |
27 | echo "ok - Test --userns"
28 | fi
29 |
--------------------------------------------------------------------------------
/network.h:
--------------------------------------------------------------------------------
1 | /* bubblewrap
2 | * Copyright (C) 2016 Alexander Larsson
3 | * SPDX-License-Identifier: LGPL-2.0-or-later
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library. If not, see .
17 | *
18 | */
19 |
20 | #pragma once
21 |
22 | void loopback_setup (void);
23 |
--------------------------------------------------------------------------------
/tests/test-specifying-pidns.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -xeuo pipefail
4 |
5 | srcd=$(cd $(dirname "$0") && pwd)
6 | . "${srcd}/libtest.sh"
7 |
8 | echo "1..1"
9 |
10 | # This test needs user namespaces
11 | if test -n "${bwrap_is_suid:-}"; then
12 | echo "ok - # SKIP no setuid support for --unshare-user"
13 | else
14 | mkfifo donepipe
15 | $RUN --info-fd 42 --unshare-user --unshare-pid sh -c 'readlink /proc/self/ns/pid > sandbox-pidns; cat < donepipe' >/dev/null 42>info.json &
16 | while ! test -f sandbox-pidns; do sleep 1; done
17 | SANDBOX1PID=$(extract_child_pid info.json)
18 |
19 | ASAN_OPTIONS=detect_leaks=0 LSAN_OPTIONS=detect_leaks=0 \
20 | $RUN --userns 11 --pidns 12 readlink /proc/self/ns/pid > sandbox2-pidns 11< /proc/$SANDBOX1PID/ns/user 12< /proc/$SANDBOX1PID/ns/pid
21 | echo foo > donepipe
22 |
23 | assert_files_equal sandbox-pidns sandbox2-pidns
24 |
25 | rm donepipe info.json sandbox-pidns
26 |
27 | echo "ok - Test --pidns"
28 | fi
29 |
--------------------------------------------------------------------------------
/demos/userns-block-fd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os, select, subprocess, sys, json
4 |
5 | pipe_info = os.pipe()
6 | userns_block = os.pipe()
7 |
8 | pid = os.fork()
9 |
10 | if pid != 0:
11 | os.close(pipe_info[1])
12 | os.close(userns_block[0])
13 |
14 | select.select([pipe_info[0]], [], [])
15 |
16 | data = json.load(os.fdopen(pipe_info[0]))
17 | child_pid = str(data['child-pid'])
18 |
19 | subprocess.call(["newuidmap", child_pid, "0", str(os.getuid()), "1"])
20 | subprocess.call(["newgidmap", child_pid, "0", str(os.getgid()), "1"])
21 |
22 | os.write(userns_block[1], b'1')
23 | else:
24 | os.close(pipe_info[0])
25 | os.close(userns_block[1])
26 |
27 | os.set_inheritable(pipe_info[1], True)
28 | os.set_inheritable(userns_block[0], True)
29 |
30 | args = ["bwrap",
31 | "bwrap",
32 | "--unshare-all",
33 | "--unshare-user",
34 | "--userns-block-fd", "%i" % userns_block[0],
35 | "--info-fd", "%i" % pipe_info[1],
36 | "--bind", "/", "/",
37 | "cat", "/proc/self/uid_map"]
38 |
39 | os.execlp(*args)
40 |
--------------------------------------------------------------------------------
/completions/bash/meson.build:
--------------------------------------------------------------------------------
1 | bash_completion_dir = get_option('bash_completion_dir')
2 |
3 | if bash_completion_dir == ''
4 | bash_completion = dependency(
5 | 'bash-completion',
6 | version : '>=2.0',
7 | required : false,
8 | )
9 |
10 | if bash_completion.found()
11 | if meson.version().version_compare('>=0.51.0')
12 | bash_completion_dir = bash_completion.get_variable(
13 | default_value: '',
14 | pkgconfig: 'completionsdir',
15 | pkgconfig_define: [
16 | 'prefix', get_option('prefix'),
17 | 'datadir', get_option('prefix') / get_option('datadir'),
18 | ],
19 | )
20 | else
21 | bash_completion_dir = bash_completion.get_pkgconfig_variable(
22 | 'completionsdir',
23 | default: '',
24 | define_variable: [
25 | 'datadir', get_option('prefix') / get_option('datadir'),
26 | ],
27 | )
28 | endif
29 | endif
30 | endif
31 |
32 | if bash_completion_dir == ''
33 | bash_completion_dir = get_option('datadir') / 'bash-completion' / 'completions'
34 | endif
35 |
36 | install_data('bwrap', install_dir : bash_completion_dir)
37 |
--------------------------------------------------------------------------------
/demos/bubblewrap-shell.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Use bubblewrap to run /bin/sh reusing the host OS binaries (/usr), but with
3 | # separate /tmp, /home, /var, /run, and /etc. For /etc we just inherit the
4 | # host's resolv.conf, and set up "stub" passwd/group files. Not sharing
5 | # /home for example is intentional. If you wanted to, you could design
6 | # a bwrap-using program that shared individual parts of /home, perhaps
7 | # public content.
8 | #
9 | # Another way to build on this example is to remove --share-net to disable
10 | # networking.
11 | set -euo pipefail
12 | (exec bwrap --ro-bind /usr /usr \
13 | --dir /tmp \
14 | --dir /var \
15 | --symlink ../tmp var/tmp \
16 | --proc /proc \
17 | --dev /dev \
18 | --ro-bind /etc/resolv.conf /etc/resolv.conf \
19 | --symlink usr/lib /lib \
20 | --symlink usr/lib64 /lib64 \
21 | --symlink usr/bin /bin \
22 | --symlink usr/sbin /sbin \
23 | --chdir / \
24 | --unshare-all \
25 | --share-net \
26 | --die-with-parent \
27 | --dir /run/user/$(id -u) \
28 | --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \
29 | --setenv PS1 "bwrap-demo$ " \
30 | --file 11 /etc/passwd \
31 | --file 12 /etc/group \
32 | /bin/sh) \
33 | 11< <(getent passwd $UID 65534) \
34 | 12< <(getent group $(id -g) 65534)
35 |
--------------------------------------------------------------------------------
/completions/bash/bwrap:
--------------------------------------------------------------------------------
1 | # shellcheck shell=bash
2 |
3 | # bash completion file for bubblewrap commands
4 | #
5 |
6 | _bwrap() {
7 | local cur prev words cword
8 | _init_completion || return
9 |
10 | # Please keep sorted in LC_ALL=C order
11 | local boolean_options="
12 | --as-pid-1
13 | --assert-userns-disabled
14 | --clearenv
15 | --disable-userns
16 | --help
17 | --new-session
18 | --unshare-all
19 | --unshare-cgroup
20 | --unshare-cgroup-try
21 | --unshare-ipc
22 | --unshare-net
23 | --unshare-pid
24 | --unshare-user
25 | --unshare-user-try
26 | --unshare-uts
27 | --version
28 | "
29 |
30 | # Please keep sorted in LC_ALL=C order
31 | local options_with_args="
32 | $boolean_optons
33 | --add-seccomp-fd
34 | --args
35 | --argv0
36 | --bind
37 | --bind-data
38 | --block-fd
39 | --cap-add
40 | --cap-drop
41 | --chdir
42 | --chmod
43 | --dev
44 | --dev-bind
45 | --die-with-parent
46 | --dir
47 | --exec-label
48 | --file
49 | --file-label
50 | --gid
51 | --hostname
52 | --info-fd
53 | --lock-file
54 | --perms
55 | --proc
56 | --remount-ro
57 | --ro-bind
58 | --seccomp
59 | --setenv
60 | --size
61 | --symlink
62 | --sync-fd
63 | --uid
64 | --unsetenv
65 | --userns-block-fd
66 | "
67 |
68 | if [[ "$cur" == -* ]]; then
69 | COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
70 | fi
71 |
72 | return 0
73 | }
74 | complete -F _bwrap bwrap
75 |
76 | # vim:set ft=bash:
77 |
--------------------------------------------------------------------------------
/packaging/bubblewrap.spec:
--------------------------------------------------------------------------------
1 | %global commit0 66d12bb23b04e201c5846e325f0b10930ed802f8
2 | %global shortcommit0 %(c=%{commit0}; echo ${c:0:7})
3 |
4 | Summary: Core execution tool for unprivileged containers
5 | Name: bubblewrap
6 | Version: 0
7 | Release: 1%{?dist}
8 | #VCS: git:https://github.com/projectatomic/bubblewrap
9 | Source0: https://github.com/projectatomic/%{name}/archive/%{commit0}.tar.gz#/%{name}-%{shortcommit0}.tar.gz
10 | License: LGPLv2+
11 | URL: https://github.com/projectatomic/bubblewrap
12 |
13 | BuildRequires: git
14 | # We always run autogen.sh
15 | BuildRequires: autoconf automake libtool
16 | BuildRequires: libcap-devel
17 | BuildRequires: pkgconfig(libselinux)
18 | BuildRequires: libxslt
19 | BuildRequires: docbook-style-xsl
20 |
21 | %description
22 | Bubblewrap (/usr/bin/bwrap) is a core execution engine for unprivileged
23 | containers that works as a setuid binary on kernels without
24 | user namespaces.
25 |
26 | %prep
27 | %autosetup -Sgit -n %{name}-%{version}
28 |
29 | %build
30 | env NOCONFIGURE=1 ./autogen.sh
31 | %configure --disable-silent-rules --with-priv-mode=none
32 |
33 | make %{?_smp_mflags}
34 |
35 | %install
36 | make install DESTDIR=$RPM_BUILD_ROOT INSTALL="install -p -c"
37 | find $RPM_BUILD_ROOT -name '*.la' -delete
38 |
39 | %files
40 | %license COPYING
41 | %doc README.md
42 | %{_datadir}/bash-completion/completions/bwrap
43 | %if (0%{?rhel} != 0 && 0%{?rhel} <= 7)
44 | %attr(4755,root,root) %{_bindir}/bwrap
45 | %else
46 | %{_bindir}/bwrap
47 | %endif
48 | %{_mandir}/man1/*
49 |
--------------------------------------------------------------------------------
/tests/meson.build:
--------------------------------------------------------------------------------
1 | test_programs = [
2 | ['test-utils', executable(
3 | 'test-utils',
4 | 'test-utils.c',
5 | '../utils.c',
6 | '../utils.h',
7 | dependencies : [selinux_dep],
8 | include_directories : common_include_directories,
9 | )],
10 | ]
11 |
12 | executable(
13 | 'try-syscall',
14 | 'try-syscall.c',
15 | override_options: ['b_sanitize=none'],
16 | )
17 |
18 | test_scripts = [
19 | 'test-run.sh',
20 | 'test-seccomp.py',
21 | 'test-specifying-pidns.sh',
22 | 'test-specifying-userns.sh',
23 | ]
24 |
25 | test_env = environment()
26 | test_env.set('BWRAP', bwrap.full_path())
27 | test_env.set('G_TEST_BUILDDIR', meson.current_build_dir() / '..')
28 | test_env.set('G_TEST_SRCDIR', meson.current_source_dir() / '..')
29 |
30 | foreach pair : test_programs
31 | name = pair[0]
32 | test_program = pair[1]
33 | if meson.version().version_compare('>=0.50.0')
34 | test(
35 | name,
36 | test_program,
37 | env : test_env,
38 | protocol : 'tap',
39 | )
40 | else
41 | test(
42 | name,
43 | test_program,
44 | env : test_env,
45 | )
46 | endif
47 | endforeach
48 |
49 | foreach test_script : test_scripts
50 | if test_script.endswith('.py')
51 | interpreter = python
52 | else
53 | interpreter = bash
54 | endif
55 |
56 | if meson.version().version_compare('>=0.50.0')
57 | test(
58 | test_script,
59 | interpreter,
60 | args : [files(test_script)],
61 | env : test_env,
62 | protocol : 'tap',
63 | )
64 | else
65 | test(
66 | test_script,
67 | interpreter,
68 | args : [files(test_script)],
69 | env : test_env,
70 | )
71 | endif
72 | endforeach
73 |
--------------------------------------------------------------------------------
/meson_options.txt:
--------------------------------------------------------------------------------
1 | option(
2 | 'bash_completion',
3 | type : 'feature',
4 | description : 'install bash completion script',
5 | value : 'enabled',
6 | )
7 | option(
8 | 'bash_completion_dir',
9 | type : 'string',
10 | description : 'install bash completion script in this directory',
11 | value : '',
12 | )
13 | option(
14 | 'bwrapdir',
15 | type : 'string',
16 | description : 'install bwrap in this directory [default: bindir, or libexecdir in subprojects]',
17 | )
18 | option(
19 | 'build_rpath',
20 | type : 'string',
21 | description : 'set a RUNPATH or RPATH on the bwrap executable',
22 | )
23 | option(
24 | 'install_rpath',
25 | type : 'string',
26 | description : 'set a RUNPATH or RPATH on the bwrap executable',
27 | )
28 | option(
29 | 'man',
30 | type : 'feature',
31 | description : 'generate man pages',
32 | value : 'auto',
33 | )
34 | option(
35 | 'program_prefix',
36 | type : 'string',
37 | description : 'Prepend string to bwrap executable name, for use with subprojects',
38 | )
39 | option(
40 | 'python',
41 | type : 'string',
42 | description : 'Path to Python 3, or empty to use python3',
43 | )
44 | option(
45 | 'require_userns',
46 | type : 'boolean',
47 | description : 'require user namespaces by default when installed setuid',
48 | value : 'false',
49 | )
50 | option(
51 | 'selinux',
52 | type : 'feature',
53 | description : 'enable optional SELINUX support',
54 | value : 'auto',
55 | )
56 | option(
57 | 'tests',
58 | type : 'boolean',
59 | description : 'build tests',
60 | value : 'true',
61 | )
62 | option(
63 | 'zsh_completion',
64 | type : 'feature',
65 | description : 'install zsh completion script',
66 | value : 'enabled',
67 | )
68 | option(
69 | 'zsh_completion_dir',
70 | type : 'string',
71 | description : 'install zsh completion script in this directory',
72 | value : '',
73 | )
74 |
--------------------------------------------------------------------------------
/bind-mount.h:
--------------------------------------------------------------------------------
1 | /* bubblewrap
2 | * Copyright (C) 2016 Alexander Larsson
3 | * SPDX-License-Identifier: LGPL-2.0-or-later
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library. If not, see .
17 | *
18 | */
19 |
20 | #pragma once
21 |
22 | #include "utils.h"
23 |
24 | typedef enum {
25 | BIND_READONLY = (1 << 0),
26 | BIND_DEVICES = (1 << 2),
27 | BIND_RECURSIVE = (1 << 3),
28 | } bind_option_t;
29 |
30 | typedef enum
31 | {
32 | BIND_MOUNT_SUCCESS = 0,
33 | BIND_MOUNT_ERROR_MOUNT,
34 | BIND_MOUNT_ERROR_REALPATH_DEST,
35 | BIND_MOUNT_ERROR_REOPEN_DEST,
36 | BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD,
37 | BIND_MOUNT_ERROR_FIND_DEST_MOUNT,
38 | BIND_MOUNT_ERROR_REMOUNT_DEST,
39 | BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT,
40 | } bind_mount_result;
41 |
42 | bind_mount_result bind_mount (int proc_fd,
43 | const char *src,
44 | const char *dest,
45 | bind_option_t options,
46 | char **failing_path);
47 |
48 | void die_with_bind_result (bind_mount_result res,
49 | int saved_errno,
50 | const char *failing_path,
51 | const char *format,
52 | ...)
53 | __attribute__((__noreturn__))
54 | __attribute__((format (printf, 4, 5)));
55 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | ## Security and Disclosure Information Policy for the bubblewrap Project
2 |
3 | The bubblewrap Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/HEAD/SECURITY.md) for the Containers Projects.
4 |
5 | ### System security
6 |
7 | If bubblewrap is setuid root, then the goal is that it does not allow
8 | a malicious local user to do anything that would not have been possible
9 | on a kernel that allows unprivileged users to create new user namespaces.
10 | For example, [CVE-2020-5291](https://github.com/containers/bubblewrap/security/advisories/GHSA-j2qp-rvxj-43vj)
11 | was treated as a security vulnerability in bubblewrap.
12 |
13 | If bubblewrap is not setuid root, then it is not a security boundary
14 | between the user and the OS, because anything bubblewrap could do, a
15 | malicious user could equally well do by writing their own tool equivalent
16 | to bubblewrap.
17 |
18 | ### Sandbox security
19 |
20 | bubblewrap is a toolkit for constructing sandbox environments.
21 | bubblewrap is not a complete, ready-made sandbox with a specific security
22 | policy.
23 |
24 | Some of bubblewrap's use-cases want a security boundary between the sandbox
25 | and the real system; other use-cases want the ability to change the layout of
26 | the filesystem for processes inside the sandbox, but do not aim to be a
27 | security boundary.
28 | As a result, the level of protection between the sandboxed processes and
29 | the host system is entirely determined by the arguments passed to
30 | bubblewrap.
31 |
32 | Whatever program constructs the command-line arguments for bubblewrap
33 | (often a larger framework like Flatpak, libgnome-desktop, sandwine
34 | or an ad-hoc script) is responsible for defining its own security model,
35 | and choosing appropriate bubblewrap command-line arguments to implement
36 | that security model.
37 |
38 | For example,
39 | [CVE-2017-5226](https://github.com/flatpak/flatpak/security/advisories/GHSA-7gfv-rvfx-h87x)
40 | (in which a Flatpak app could send input to a parent terminal using the
41 | `TIOCSTI` ioctl) is considered to be a Flatpak vulnerability, not a
42 | bubblewrap vulnerability.
43 |
--------------------------------------------------------------------------------
/ci/builddeps.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Copyright 2021 Simon McVittie
3 | # SPDX-License-Identifier: LGPL-2.0-or-later
4 |
5 | set -eux
6 | set -o pipefail
7 |
8 | usage() {
9 | if [ "${1-2}" -ne 0 ]; then
10 | exec >&2
11 | fi
12 | cat <&2
46 | usage 2
47 | ;;
48 | esac
49 | done
50 |
51 | # No more arguments please
52 | for arg in "$@"; do
53 | usage 2
54 | done
55 |
56 | if dpkg-vendor --derives-from Debian; then
57 | apt-get -y update
58 | apt-get -q -y install \
59 | autoconf \
60 | automake \
61 | build-essential \
62 | docbook-xml \
63 | docbook-xsl \
64 | libcap-dev \
65 | libselinux1-dev \
66 | libtool \
67 | meson \
68 | pkg-config \
69 | python3 \
70 | xsltproc \
71 | ${NULL+}
72 |
73 | if [ -n "${opt_clang}" ]; then
74 | apt-get -y install clang
75 | fi
76 |
77 | exit 0
78 | fi
79 |
80 | if command -v yum; then
81 | yum -y install \
82 | 'pkgconfig(libselinux)' \
83 | /usr/bin/eu-readelf \
84 | autoconf \
85 | automake \
86 | docbook-style-xsl \
87 | gcc \
88 | git \
89 | libasan \
90 | libcap-devel \
91 | libtool \
92 | libtsan \
93 | libubsan \
94 | libxslt \
95 | make \
96 | meson \
97 | redhat-rpm-config \
98 | rsync \
99 | ${NULL+}
100 |
101 | if [ -n "${opt_clang}" ]; then
102 | yum -y install clang
103 | fi
104 |
105 | exit 0
106 | fi
107 |
108 | echo "Unknown distribution" >&2
109 | exit 1
110 |
111 | # vim:set sw=4 sts=4 et:
112 |
--------------------------------------------------------------------------------
/Makefile.am:
--------------------------------------------------------------------------------
1 | AM_CFLAGS = $(WARN_CFLAGS)
2 | CLEANFILES =
3 | EXTRA_DIST = \
4 | .dir-locals.el \
5 | .editorconfig \
6 | README.md \
7 | autogen.sh \
8 | completions/bash/meson.build \
9 | completions/meson.build \
10 | completions/zsh/meson.build \
11 | demos/bubblewrap-shell.sh \
12 | demos/flatpak-run.sh \
13 | demos/flatpak.bpf \
14 | demos/userns-block-fd.py \
15 | meson.build \
16 | meson_options.txt \
17 | packaging/bubblewrap.spec \
18 | tests/meson.build \
19 | tests/use-as-subproject/README \
20 | tests/use-as-subproject/config.h \
21 | tests/use-as-subproject/dummy-config.h.in \
22 | tests/use-as-subproject/meson.build \
23 | uncrustify.cfg \
24 | uncrustify.sh \
25 | $(NULL)
26 |
27 | GITIGNOREFILES = build-aux/ gtk-doc.make config.h.in aclocal.m4
28 |
29 | bin_PROGRAMS = bwrap
30 |
31 | bwrap_srcpath := $(srcdir)
32 | include Makefile-bwrap.am
33 |
34 | install-exec-hook:
35 | if PRIV_MODE_SETUID
36 | $(SUDO_BIN) chown root $(DESTDIR)$(bindir)/bwrap
37 | $(SUDO_BIN) chmod u+s $(DESTDIR)$(bindir)/bwrap
38 | endif
39 |
40 | test_programs = \
41 | tests/test-utils \
42 | $(NULL)
43 | test_scripts = \
44 | tests/test-run.sh \
45 | tests/test-seccomp.py \
46 | tests/test-specifying-userns.sh \
47 | tests/test-specifying-pidns.sh \
48 | $(NULL)
49 | test_extra_programs = \
50 | test-bwrap \
51 | tests/try-syscall \
52 | $(NULL)
53 |
54 | test-bwrap$(EXEEXT): bwrap
55 | rm -rf test-bwrap
56 | cp bwrap test-bwrap
57 | chmod 0755 test-bwrap
58 | if PRIV_MODE_SETUID
59 | $(SUDO_BIN) chown root test-bwrap
60 | $(SUDO_BIN) chmod u+s test-bwrap
61 | endif
62 |
63 | tests_test_utils_SOURCES = \
64 | tests/test-utils.c \
65 | utils.h \
66 | utils.c \
67 | $(NULL)
68 | tests_test_utils_LDADD = $(SELINUX_LIBS)
69 |
70 | test_bwrap_SOURCES=
71 |
72 | include Makefile-docs.am
73 |
74 | LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
75 | LOG_COMPILER =
76 | TESTS_ENVIRONMENT = \
77 | BWRAP=$(abs_top_builddir)/test-bwrap \
78 | G_TEST_BUILDDIR=$(abs_top_builddir) \
79 | G_TEST_SRCDIR=$(abs_top_srcdir) \
80 | $(NULL)
81 | check_PROGRAMS = $(test_programs) $(test_extra_programs)
82 | TESTS = $(test_programs) $(test_scripts)
83 |
84 | EXTRA_DIST += $(test_scripts)
85 | EXTRA_DIST += tests/libtest-core.sh
86 | EXTRA_DIST += tests/libtest.sh
87 |
88 | if ENABLE_BASH_COMPLETION
89 | bashcompletiondir = $(BASH_COMPLETION_DIR)
90 | dist_bashcompletion_DATA = completions/bash/bwrap
91 | endif
92 |
93 | if ENABLE_ZSH_COMPLETION
94 | zshcompletiondir = $(ZSH_COMPLETION_DIR)
95 | dist_zshcompletion_DATA = completions/zsh/_bwrap
96 | endif
97 |
98 | -include $(top_srcdir)/git.mk
99 |
100 | AM_DISTCHECK_CONFIGURE_FLAGS = \
101 | --with-bash-completion-dir="\$(datadir)"/bash-completion/ \
102 | $(NULL)
103 |
--------------------------------------------------------------------------------
/demos/flatpak-run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # For this to work you first have to run these commands:
3 | # curl -O http://sdk.gnome.org/nightly/keys/nightly.gpg
4 | # flatpak --user remote-add --gpg-key=nightly.gpg gnome-nightly http://sdk.gnome.org/nightly/repo/
5 | # flatpak --user install gnome-nightly org.gnome.Platform
6 | # flatpak --user install gnome-nightly org.gnome.Weather
7 |
8 | mkdir -p ~/.var/app/org.gnome.Weather/cache ~/.var/app/org.gnome.Weather/config ~/.var/app/org.gnome.Weather/data
9 |
10 | (
11 | exec bwrap \
12 | --ro-bind ~/.local/share/flatpak/runtime/org.gnome.Platform/x86_64/master/active/files /usr \
13 | --lock-file /usr/.ref \
14 | --ro-bind ~/.local/share/flatpak/app/org.gnome.Weather/x86_64/master/active/files/ /app \
15 | --lock-file /app/.ref \
16 | --dev /dev \
17 | --proc /proc \
18 | --dir /tmp \
19 | --symlink /tmp /var/tmp \
20 | --symlink /run /var/run \
21 | --symlink usr/lib /lib \
22 | --symlink usr/lib64 /lib64 \
23 | --symlink usr/bin /bin \
24 | --symlink usr/sbin /sbin \
25 | --symlink usr/etc /etc \
26 | --dir /run/user/`id -u` \
27 | --ro-bind /etc/machine-id /usr/etc/machine-id \
28 | --ro-bind /etc/resolv.conf /run/host/monitor/resolv.conf \
29 | --ro-bind /sys/block /sys/block \
30 | --ro-bind /sys/bus /sys/bus \
31 | --ro-bind /sys/class /sys/class \
32 | --ro-bind /sys/dev /sys/dev \
33 | --ro-bind /sys/devices /sys/devices \
34 | --dev-bind /dev/dri /dev/dri \
35 | --bind /tmp/.X11-unix/X0 /tmp/.X11-unix/X99 \
36 | --bind ~/.var/app/org.gnome.Weather ~/.var/app/org.gnome.Weather \
37 | --bind ~/.config/dconf ~/.config/dconf \
38 | --bind /run/user/`id -u`/dconf /run/user/`id -u`/dconf \
39 | --unshare-pid \
40 | --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \
41 | --setenv DISPLAY :99 \
42 | --setenv GI_TYPELIB_PATH /app/lib/girepository-1.0 \
43 | --setenv GST_PLUGIN_PATH /app/lib/gstreamer-1.0 \
44 | --setenv LD_LIBRARY_PATH /app/lib:/usr/lib/GL \
45 | --setenv DCONF_USER_CONFIG_DIR .config/dconf \
46 | --setenv PATH /app/bin:/usr/bin \
47 | --setenv XDG_CONFIG_DIRS /app/etc/xdg:/etc/xdg \
48 | --setenv XDG_DATA_DIRS /app/share:/usr/share \
49 | --setenv SHELL /bin/sh \
50 | --setenv XDG_CACHE_HOME ~/.var/app/org.gnome.Weather/cache \
51 | --setenv XDG_CONFIG_HOME ~/.var/app/org.gnome.Weather/config \
52 | --setenv XDG_DATA_HOME ~/.var/app/org.gnome.Weather/data \
53 | --file 10 /run/user/`id -u`/flatpak-info \
54 | --bind-data 11 /usr/etc/passwd \
55 | --bind-data 12 /usr/etc/group \
56 | --seccomp 13 \
57 | /bin/sh) \
58 | 11< <(getent passwd $UID 65534 ) \
59 | 12< <(getent group $(id -g) 65534) \
60 | 13< `dirname $0`/flatpak.bpf \
61 | 10<
7 | # SPDX-License-Identifier: LGPL-2.0-or-later
8 | #
9 | # This library is free software; you can redistribute it and/or
10 | # modify it under the terms of the GNU Lesser General Public
11 | # License as published by the Free Software Foundation; either
12 | # version 2 of the License, or (at your option) any later version.
13 | #
14 | # This library is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 | # Lesser General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU Lesser General Public
20 | # License along with this library; if not, write to the
21 | # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 | # Boston, MA 02111-1307, USA.
23 |
24 | set -e
25 |
26 | if [ -n "${G_TEST_SRCDIR:-}" ]; then
27 | test_srcdir="${G_TEST_SRCDIR}/tests"
28 | else
29 | test_srcdir=$(dirname "$0")
30 | fi
31 |
32 | if [ -n "${G_TEST_BUILDDIR:-}" ]; then
33 | test_builddir="${G_TEST_BUILDDIR}/tests"
34 | else
35 | test_builddir=$(dirname "$0")
36 | fi
37 |
38 | . "${test_srcdir}/libtest-core.sh"
39 |
40 | # Make sure /sbin/getpcaps etc. are in our PATH even if non-root
41 | PATH="$PATH:/usr/sbin:/sbin"
42 |
43 | tempdir=$(mktemp -d /var/tmp/tap-test.XXXXXX)
44 | touch "${tempdir}/.testtmp"
45 | cleanup() {
46 | if test -n "${TEST_SKIP_CLEANUP:-}"; then
47 | echo "Skipping cleanup of ${tempdir}"
48 | elif test -f "${tempdir}/.testtmp"; then
49 | rm -rf "${tempdir}"
50 | fi
51 | }
52 | trap cleanup EXIT
53 | cd "${tempdir}"
54 |
55 | : "${BWRAP:=bwrap}"
56 | if test -u "$(type -p ${BWRAP})"; then
57 | bwrap_is_suid=true
58 | fi
59 |
60 | FUSE_DIR=
61 | for mp in $(grep " fuse[. ]" /proc/self/mounts | grep "user_id=$(id -u)" | awk '{print $2}'); do
62 | if test -d "$mp"; then
63 | echo "# Using $mp as test fuse mount"
64 | FUSE_DIR="$mp"
65 | break
66 | fi
67 | done
68 |
69 | if test "$(id -u)" = "0"; then
70 | is_uidzero=true
71 | else
72 | is_uidzero=false
73 | fi
74 |
75 | # This is supposed to be an otherwise readable file in an unreadable (by the user) dir
76 | UNREADABLE=/root/.bashrc
77 | if "${is_uidzero}" || test -x "$(dirname "$UNREADABLE")"; then
78 | UNREADABLE=
79 | fi
80 |
81 | # https://github.com/projectatomic/bubblewrap/issues/217
82 | # are we on a merged-/usr system?
83 | if [ /lib -ef /usr/lib ]; then
84 | BWRAP_RO_HOST_ARGS="--ro-bind /usr /usr
85 | --ro-bind /etc /etc
86 | --dir /var/tmp
87 | --symlink usr/lib /lib
88 | --symlink usr/lib64 /lib64
89 | --symlink usr/bin /bin
90 | --symlink usr/sbin /sbin
91 | --proc /proc
92 | --dev /dev"
93 | else
94 | BWRAP_RO_HOST_ARGS="--ro-bind /usr /usr
95 | --ro-bind /etc /etc
96 | --ro-bind /bin /bin
97 | --ro-bind-try /lib /lib
98 | --ro-bind-try /lib64 /lib64
99 | --ro-bind-try /sbin /sbin
100 | --ro-bind-try /nix/store /nix/store
101 | --dir /var/tmp
102 | --proc /proc
103 | --dev /dev"
104 | fi
105 |
106 | # Default arg, bind whole host fs to /, tmpfs on /tmp
107 | RUN="${BWRAP} --bind / / --tmpfs /tmp"
108 |
109 | if [ -z "${BWRAP_MUST_WORK-}" ] && ! $RUN true; then
110 | skip Seems like bwrap is not working at all. Maybe setuid is not working
111 | fi
112 |
113 | extract_child_pid() {
114 | grep child-pid "$1" | sed "s/^.*: \([0-9]*\).*/\1/"
115 | }
116 |
--------------------------------------------------------------------------------
/uncrustify.cfg:
--------------------------------------------------------------------------------
1 | newlines lf
2 |
3 | input_tab_size 8
4 | output_tab_size 8
5 |
6 | string_escape_char 92
7 | string_escape_char2 0
8 |
9 | # indenting
10 | indent_columns 2
11 | indent_with_tabs 0
12 | indent_align_string True
13 | indent_brace 2
14 | indent_braces false
15 | indent_braces_no_func True
16 | indent_func_call_param false
17 | indent_func_def_param false
18 | indent_func_proto_param false
19 | indent_switch_case 0
20 | indent_case_brace 2
21 | indent_paren_close 1
22 |
23 | # spacing
24 | sp_arith Add
25 | sp_assign Add
26 | sp_enum_assign Add
27 | sp_bool Add
28 | sp_compare Add
29 | sp_inside_paren Remove
30 | sp_inside_fparens Remove
31 | sp_func_def_paren Force
32 | sp_func_proto_paren Force
33 | sp_paren_paren Remove
34 | sp_balance_nested_parens False
35 | sp_paren_brace Remove
36 | sp_before_square Remove
37 | sp_before_squares Remove
38 | sp_inside_square Remove
39 | sp_before_ptr_star Add
40 | sp_between_ptr_star Remove
41 | sp_after_comma Add
42 | sp_before_comma Remove
43 | sp_after_cast Add
44 | sp_sizeof_paren Add
45 | sp_not Remove
46 | sp_inv Remove
47 | sp_addr Remove
48 | sp_member Remove
49 | sp_deref Remove
50 | sp_sign Remove
51 | sp_incdec Remove
52 | sp_attribute_paren remove
53 | sp_macro Force
54 | sp_func_call_paren Force
55 | sp_func_call_user_paren Remove
56 | set func_call_user _ N_ C_ g_autoptr g_auto
57 | sp_brace_typedef add
58 | sp_cond_colon add
59 | sp_cond_question add
60 | sp_defined_paren remove
61 |
62 | # alignment
63 | align_keep_tabs False
64 | align_with_tabs False
65 | align_on_tabstop False
66 | align_number_left True
67 | align_func_params True
68 | align_var_def_span 0
69 | align_var_def_amp_style 1
70 | align_var_def_colon true
71 | align_enum_equ_span 0
72 | align_var_struct_span 2
73 | align_var_def_star_style 2
74 | align_var_def_amp_style 2
75 | align_typedef_span 2
76 | align_typedef_func 0
77 | align_typedef_star_style 2
78 | align_typedef_amp_style 2
79 |
80 | # newlines
81 | nl_assign_leave_one_liners True
82 | nl_enum_leave_one_liners False
83 | nl_func_leave_one_liners False
84 | nl_if_leave_one_liners False
85 | nl_end_of_file Add
86 | nl_assign_brace Remove
87 | nl_func_var_def_blk 1
88 | nl_fcall_brace Add
89 | nl_enum_brace Remove
90 | nl_struct_brace Force
91 | nl_union_brace Force
92 | nl_if_brace Force
93 | nl_brace_else Force
94 | nl_elseif_brace Force
95 | nl_else_brace Add
96 | nl_for_brace Force
97 | nl_while_brace Force
98 | nl_do_brace Force
99 | nl_brace_while Force
100 | nl_switch_brace Force
101 | nl_before_case True
102 | nl_after_case False
103 | nl_func_type_name Force
104 | nl_func_proto_type_name Remove
105 | nl_func_paren Remove
106 | nl_func_decl_start Remove
107 | nl_func_decl_args Force
108 | nl_func_decl_end Remove
109 | nl_fdef_brace Force
110 | nl_after_return False
111 | nl_define_macro False
112 | nl_create_if_one_liner False
113 | nl_create_for_one_liner False
114 | nl_create_while_one_liner False
115 | nl_after_semicolon True
116 | nl_multi_line_cond true
117 |
118 | # mod
119 | # I'd like these to be remove, but that removes brackets in if { if { foo } }, which i dislike
120 | # Not clear what to do about that...
121 | mod_full_brace_for Remove
122 | mod_full_brace_if Remove
123 | mod_full_brace_if_chain True
124 | mod_full_brace_while Remove
125 | mod_full_brace_do Remove
126 | mod_full_brace_nl 3
127 | mod_paren_on_return Remove
128 |
129 | # line splitting
130 | #code_width = 78
131 | ls_for_split_full True
132 | ls_func_split_full True
133 |
134 | # positioning
135 | pos_bool Trail
136 | pos_conditional Trail
137 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project(
2 | 'bubblewrap',
3 | 'c',
4 | version : '0.10.0',
5 | meson_version : '>=0.49.0',
6 | default_options : [
7 | 'warning_level=2',
8 | ],
9 | )
10 |
11 | cc = meson.get_compiler('c')
12 | add_project_arguments('-D_GNU_SOURCE', language : 'c')
13 | common_include_directories = include_directories('.')
14 |
15 | # Keep this in sync with ostree, except remove -Wall (part of Meson
16 | # warning_level 2) and -Werror=declaration-after-statement
17 | add_project_arguments(
18 | cc.get_supported_arguments([
19 | '-Werror=shadow',
20 | '-Werror=empty-body',
21 | '-Werror=strict-prototypes',
22 | '-Werror=missing-prototypes',
23 | '-Werror=implicit-function-declaration',
24 | '-Werror=pointer-arith',
25 | '-Werror=init-self',
26 | '-Werror=missing-declarations',
27 | '-Werror=return-type',
28 | '-Werror=overflow',
29 | '-Werror=int-conversion',
30 | '-Werror=parenthesis',
31 | '-Werror=incompatible-pointer-types',
32 | '-Werror=misleading-indentation',
33 | '-Werror=missing-include-dirs',
34 | '-Werror=aggregate-return',
35 |
36 | # Extra warnings specific to bubblewrap
37 | '-Werror=switch-default',
38 | '-Wswitch-enum',
39 |
40 | # Deliberately not warning about these, ability to zero-initialize
41 | # a struct is a feature
42 | '-Wno-missing-field-initializers',
43 | '-Wno-error=missing-field-initializers',
44 | ]),
45 | language : 'c',
46 | )
47 |
48 | if (
49 | cc.has_argument('-Werror=format=2')
50 | and cc.has_argument('-Werror=format-security')
51 | and cc.has_argument('-Werror=format-nonliteral')
52 | )
53 | add_project_arguments([
54 | '-Werror=format=2',
55 | '-Werror=format-security',
56 | '-Werror=format-nonliteral',
57 | ], language : 'c')
58 | endif
59 |
60 | bash = find_program('bash', required : false)
61 |
62 | if get_option('python') == ''
63 | python = find_program('python3')
64 | else
65 | python = find_program(get_option('python'))
66 | endif
67 |
68 | libcap_dep = dependency('libcap', required : true)
69 |
70 | selinux_dep = dependency(
71 | 'libselinux',
72 | version : '>=2.1.9',
73 | # if disabled, Meson will behave as though libselinux was not found
74 | required : get_option('selinux'),
75 | )
76 |
77 | cdata = configuration_data()
78 | cdata.set_quoted(
79 | 'PACKAGE_STRING',
80 | '@0@ @1@'.format(meson.project_name(), meson.project_version()),
81 | )
82 |
83 | if selinux_dep.found()
84 | cdata.set('HAVE_SELINUX', 1)
85 | if selinux_dep.version().version_compare('>=2.3')
86 | cdata.set('HAVE_SELINUX_2_3', 1)
87 | endif
88 | endif
89 |
90 | if get_option('require_userns')
91 | cdata.set('ENABLE_REQUIRE_USERNS', 1)
92 | endif
93 |
94 | configure_file(
95 | output : 'config.h',
96 | configuration : cdata,
97 | )
98 |
99 | if meson.is_subproject() and get_option('program_prefix') == ''
100 | error('program_prefix option must be set when bwrap is a subproject')
101 | endif
102 |
103 | if get_option('bwrapdir') != ''
104 | bwrapdir = get_option('bwrapdir')
105 | elif meson.is_subproject()
106 | bwrapdir = get_option('libexecdir')
107 | else
108 | bwrapdir = get_option('bindir')
109 | endif
110 |
111 | bwrap = executable(
112 | get_option('program_prefix') + 'bwrap',
113 | [
114 | 'bubblewrap.c',
115 | 'bind-mount.c',
116 | 'network.c',
117 | 'utils.c',
118 | ],
119 | build_rpath : get_option('build_rpath'),
120 | install : true,
121 | install_dir : bwrapdir,
122 | install_rpath : get_option('install_rpath'),
123 | dependencies : [selinux_dep, libcap_dep],
124 | )
125 |
126 | manpages_xsl = 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl'
127 | xsltproc = find_program('xsltproc', required : get_option('man'))
128 | build_man_page = false
129 |
130 | if xsltproc.found() and not meson.is_subproject()
131 | if run_command([
132 | xsltproc, '--nonet', manpages_xsl,
133 | ], check : false).returncode() == 0
134 | message('Docbook XSL found, man page enabled')
135 | build_man_page = true
136 | elif get_option('man').enabled()
137 | error('Man page requested, but Docbook XSL stylesheets not found')
138 | else
139 | message('Docbook XSL not found, man page disabled automatically')
140 | endif
141 | endif
142 |
143 | if build_man_page
144 | custom_target(
145 | 'bwrap.1',
146 | output : 'bwrap.1',
147 | input : 'bwrap.xml',
148 | command : [
149 | xsltproc,
150 | '--nonet',
151 | '--stringparam', 'man.output.quietly', '1',
152 | '--stringparam', 'funcsynopsis.style', 'ansi',
153 | '--stringparam', 'man.th.extra1.suppress', '1',
154 | '--stringparam', 'man.authors.section.enabled', '0',
155 | '--stringparam', 'man.copyright.section.enabled', '0',
156 | '-o', '@OUTPUT@',
157 | manpages_xsl,
158 | '@INPUT@',
159 | ],
160 | install : true,
161 | install_dir : get_option('mandir') / 'man1',
162 | )
163 | endif
164 |
165 | if not meson.is_subproject()
166 | subdir('completions')
167 | endif
168 |
169 | if get_option('tests')
170 | subdir('tests')
171 | endif
172 |
--------------------------------------------------------------------------------
/tests/try-syscall.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Simon McVittie
3 | * SPDX-License-Identifier: LGPL-2.0-or-later
4 | *
5 | * Try one or more system calls that might have been blocked by a
6 | * seccomp filter. Return the last value of errno seen.
7 | *
8 | * In general, we pass a bad fd or pointer to each syscall that will
9 | * accept one, so that it will fail with EBADF or EFAULT without side-effects.
10 | *
11 | * This helper is used for regression tests in both bubblewrap and flatpak.
12 | * Please keep both copies in sync.
13 | */
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #if defined(_MIPS_SIM)
27 | # if _MIPS_SIM == _ABIO32
28 | # define MISSING_SYSCALL_BASE 4000
29 | # elif _MIPS_SIM == _ABI64
30 | # define MISSING_SYSCALL_BASE 5000
31 | # elif _MIPS_SIM == _ABIN32
32 | # define MISSING_SYSCALL_BASE 6000
33 | # else
34 | # error "Unknown MIPS ABI"
35 | # endif
36 | #endif
37 |
38 | #if defined(__ia64__)
39 | # define MISSING_SYSCALL_BASE 1024
40 | #endif
41 |
42 | #if defined(__alpha__)
43 | # define MISSING_SYSCALL_BASE 110
44 | #endif
45 |
46 | #if defined(__x86_64__) && defined(__ILP32__)
47 | # define MISSING_SYSCALL_BASE 0x40000000
48 | #endif
49 |
50 | /*
51 | * MISSING_SYSCALL_BASE:
52 | *
53 | * Number to add to the syscall numbers of recently-added syscalls
54 | * to get the appropriate syscall for the current ABI.
55 | */
56 | #ifndef MISSING_SYSCALL_BASE
57 | # define MISSING_SYSCALL_BASE 0
58 | #endif
59 |
60 | #ifndef __NR_clone3
61 | # define __NR_clone3 (MISSING_SYSCALL_BASE + 435)
62 | #endif
63 |
64 | /*
65 | * The size of clone3's parameter (as of 2021)
66 | */
67 | #define SIZEOF_STRUCT_CLONE_ARGS ((size_t) 88)
68 |
69 | /*
70 | * An invalid pointer that will cause syscalls to fail with EFAULT
71 | */
72 | #define WRONG_POINTER ((char *) 1)
73 |
74 | #ifndef PR_GET_CHILD_SUBREAPER
75 | #define PR_GET_CHILD_SUBREAPER 37
76 | #endif
77 |
78 | int
79 | main (int argc, char **argv)
80 | {
81 | int errsv = 0;
82 | int i;
83 |
84 | for (i = 1; i < argc; i++)
85 | {
86 | const char *arg = argv[i];
87 |
88 | if (strcmp (arg, "print-errno-values") == 0)
89 | {
90 | printf ("EBADF=%d\n", EBADF);
91 | printf ("EFAULT=%d\n", EFAULT);
92 | printf ("ENOENT=%d\n", ENOENT);
93 | printf ("ENOSYS=%d\n", ENOSYS);
94 | printf ("EPERM=%d\n", EPERM);
95 | }
96 | else if (strcmp (arg, "chmod") == 0)
97 | {
98 | /* If not blocked by seccomp, this will fail with EFAULT */
99 | if (chmod (WRONG_POINTER, 0700) != 0)
100 | {
101 | errsv = errno;
102 | perror (arg);
103 | }
104 | }
105 | else if (strcmp (arg, "chroot") == 0)
106 | {
107 | /* If not blocked by seccomp, this will fail with EFAULT */
108 | if (chroot (WRONG_POINTER) != 0)
109 | {
110 | errsv = errno;
111 | perror (arg);
112 | }
113 | }
114 | else if (strcmp (arg, "clone3") == 0)
115 | {
116 | /* If not blocked by seccomp, this will fail with EFAULT */
117 | if (syscall (__NR_clone3, WRONG_POINTER, SIZEOF_STRUCT_CLONE_ARGS) != 0)
118 | {
119 | errsv = errno;
120 | perror (arg);
121 | }
122 | }
123 | else if (strcmp (arg, "ioctl TIOCNOTTY") == 0)
124 | {
125 | /* If not blocked by seccomp, this will fail with EBADF */
126 | if (ioctl (-1, TIOCNOTTY) != 0)
127 | {
128 | errsv = errno;
129 | perror (arg);
130 | }
131 | }
132 | else if (strcmp (arg, "ioctl TIOCSTI") == 0)
133 | {
134 | /* If not blocked by seccomp, this will fail with EBADF */
135 | if (ioctl (-1, TIOCSTI, WRONG_POINTER) != 0)
136 | {
137 | errsv = errno;
138 | perror (arg);
139 | }
140 | }
141 | #ifdef __LP64__
142 | else if (strcmp (arg, "ioctl TIOCSTI CVE-2019-10063") == 0)
143 | {
144 | unsigned long not_TIOCSTI = (0x123UL << 32) | (unsigned long) TIOCSTI;
145 |
146 | /* If not blocked by seccomp, this will fail with EBADF */
147 | if (syscall (__NR_ioctl, -1, not_TIOCSTI, WRONG_POINTER) != 0)
148 | {
149 | errsv = errno;
150 | perror (arg);
151 | }
152 | }
153 | #endif
154 | else if (strcmp (arg, "listen") == 0)
155 | {
156 | /* If not blocked by seccomp, this will fail with EBADF */
157 | if (listen (-1, 42) != 0)
158 | {
159 | errsv = errno;
160 | perror (arg);
161 | }
162 | }
163 | else if (strcmp (arg, "prctl") == 0)
164 | {
165 | /* If not blocked by seccomp, this will fail with EFAULT */
166 | if (prctl (PR_GET_CHILD_SUBREAPER, WRONG_POINTER, 0, 0, 0) != 0)
167 | {
168 | errsv = errno;
169 | perror (arg);
170 | }
171 | }
172 | else
173 | {
174 | fprintf (stderr, "Unsupported syscall \"%s\"\n", arg);
175 | errsv = ENOENT;
176 | }
177 | }
178 |
179 | return errsv;
180 | }
181 |
--------------------------------------------------------------------------------
/tests/libtest-core.sh:
--------------------------------------------------------------------------------
1 | # Core source library for shell script tests; the
2 | # canonical version lives in:
3 | #
4 | # https://github.com/ostreedev/ostree
5 | #
6 | # Known copies are in the following repos:
7 | #
8 | # - https://github.com/containers/bubblewrap
9 | # - https://github.com/coreos/rpm-ostree
10 | #
11 | # Copyright (C) 2017 Colin Walters
12 | #
13 | # SPDX-License-Identifier: LGPL-2.0-or-later
14 | #
15 | # This library is free software; you can redistribute it and/or
16 | # modify it under the terms of the GNU Lesser General Public
17 | # License as published by the Free Software Foundation; either
18 | # version 2 of the License, or (at your option) any later version.
19 | #
20 | # This library is distributed in the hope that it will be useful,
21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 | # Lesser General Public License for more details.
24 | #
25 | # You should have received a copy of the GNU Lesser General Public
26 | # License along with this library; if not, write to the
27 | # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
28 | # Boston, MA 02111-1307, USA.
29 |
30 | fatal() {
31 | echo $@ 1>&2; exit 1
32 | }
33 | # fatal() is shorter to type, but retain this alias
34 | assert_not_reached () {
35 | fatal "$@"
36 | }
37 |
38 | # Some tests look for specific English strings. Use a UTF-8 version
39 | # of the C (POSIX) locale if we have one, or fall back to en_US.UTF-8
40 | # (https://sourceware.org/glibc/wiki/Proposals/C.UTF-8)
41 | #
42 | # If we can't find the locale command assume we have support for C.UTF-8
43 | # (e.g. musl based systems)
44 | if type -p locale >/dev/null; then
45 | export LC_ALL=$(locale -a | grep -iEe '^(C|en_US)\.(UTF-8|utf8)$' | head -n1 || true)
46 | if [ -z "${LC_ALL}" ]; then fatal "Can't find suitable UTF-8 locale"; fi
47 | else
48 | export LC_ALL=C.UTF-8
49 | fi
50 | # A GNU extension, used whenever LC_ALL is not C
51 | unset LANGUAGE
52 |
53 | # This should really be the default IMO
54 | export G_DEBUG=fatal-warnings
55 |
56 | assert_streq () {
57 | test "$1" = "$2" || fatal "$1 != $2"
58 | }
59 |
60 | assert_str_match () {
61 | if ! echo "$1" | grep -E -q "$2"; then
62 | fatal "$1 does not match regexp $2"
63 | fi
64 | }
65 |
66 | assert_not_streq () {
67 | (! test "$1" = "$2") || fatal "$1 == $2"
68 | }
69 |
70 | assert_has_file () {
71 | test -f "$1" || fatal "Couldn't find '$1'"
72 | }
73 |
74 | assert_has_dir () {
75 | test -d "$1" || fatal "Couldn't find '$1'"
76 | }
77 |
78 | # Dump ls -al + file contents to stderr, then fatal()
79 | _fatal_print_file() {
80 | file="$1"
81 | shift
82 | ls -al "$file" >&2
83 | sed -e 's/^/# /' < "$file" >&2
84 | fatal "$@"
85 | }
86 |
87 | _fatal_print_files() {
88 | file1="$1"
89 | shift
90 | file2="$1"
91 | shift
92 | ls -al "$file1" >&2
93 | sed -e 's/^/# /' < "$file1" >&2
94 | ls -al "$file2" >&2
95 | sed -e 's/^/# /' < "$file2" >&2
96 | fatal "$@"
97 | }
98 |
99 | assert_not_has_file () {
100 | if test -f "$1"; then
101 | _fatal_print_file "$1" "File '$1' exists"
102 | fi
103 | }
104 |
105 | assert_not_file_has_content () {
106 | fpath=$1
107 | shift
108 | for re in "$@"; do
109 | if grep -q -e "$re" "$fpath"; then
110 | _fatal_print_file "$fpath" "File '$fpath' matches regexp '$re'"
111 | fi
112 | done
113 | }
114 |
115 | assert_not_has_dir () {
116 | if test -d "$1"; then
117 | fatal "Directory '$1' exists"
118 | fi
119 | }
120 |
121 | assert_file_has_content () {
122 | fpath=$1
123 | shift
124 | for re in "$@"; do
125 | if ! grep -q -e "$re" "$fpath"; then
126 | _fatal_print_file "$fpath" "File '$fpath' doesn't match regexp '$re'"
127 | fi
128 | done
129 | }
130 |
131 | assert_file_has_content_once () {
132 | fpath=$1
133 | shift
134 | for re in "$@"; do
135 | if ! test $(grep -e "$re" "$fpath" | wc -l) = "1"; then
136 | _fatal_print_file "$fpath" "File '$fpath' doesn't match regexp '$re' exactly once"
137 | fi
138 | done
139 | }
140 |
141 | assert_file_has_content_literal () {
142 | fpath=$1; shift
143 | for s in "$@"; do
144 | if ! grep -q -F -e "$s" "$fpath"; then
145 | _fatal_print_file "$fpath" "File '$fpath' doesn't match fixed string list '$s'"
146 | fi
147 | done
148 | }
149 |
150 | assert_file_has_mode () {
151 | mode=$(stat -c '%a' $1)
152 | if [ "$mode" != "$2" ]; then
153 | fatal "File '$1' has wrong mode: expected $2, but got $mode"
154 | fi
155 | }
156 |
157 | assert_symlink_has_content () {
158 | if ! test -L "$1"; then
159 | fatal "File '$1' is not a symbolic link"
160 | fi
161 | if ! readlink "$1" | grep -q -e "$2"; then
162 | _fatal_print_file "$1" "Symbolic link '$1' doesn't match regexp '$2'"
163 | fi
164 | }
165 |
166 | assert_file_empty() {
167 | if test -s "$1"; then
168 | _fatal_print_file "$1" "File '$1' is not empty"
169 | fi
170 | }
171 |
172 | assert_files_equal() {
173 | if ! cmp "$1" "$2"; then
174 | _fatal_print_files "$1" "$2" "File '$1' and '$2' is not equal"
175 | fi
176 | }
177 |
178 | # Use to skip all of these tests
179 | skip() {
180 | echo "1..0 # SKIP" "$@"
181 | exit 0
182 | }
183 |
184 | report_err () {
185 | local exit_status="$?"
186 | { { local BASH_XTRACEFD=3; } 2> /dev/null
187 | echo "Unexpected nonzero exit status $exit_status while running: $BASH_COMMAND" >&2
188 | } 3> /dev/null
189 | }
190 | trap report_err ERR
191 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: CI checks
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | check:
13 | name: Build with Autotools and gcc, and test
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Check out
17 | uses: actions/checkout@v1
18 | - name: Install build-dependencies
19 | run: sudo ./ci/builddeps.sh
20 | - name: Create logs dir
21 | run: mkdir test-logs
22 | - name: autogen.sh
23 | run: NOCONFIGURE=1 ./autogen.sh
24 | - name: configure
25 | run: |
26 | mkdir _build
27 | pushd _build
28 | ../configure \
29 | --enable-man \
30 | --enable-selinux \
31 | ${NULL+}
32 | popd
33 | env:
34 | CFLAGS: >-
35 | -O2
36 | -Wp,-D_FORTIFY_SOURCE=2
37 | -fsanitize=address
38 | -fsanitize=undefined
39 | - name: make
40 | run: make -C _build -j $(getconf _NPROCESSORS_ONLN) V=1
41 | - name: smoke-test
42 | run: |
43 | set -x
44 | ./_build/bwrap --bind / / --tmpfs /tmp true
45 | env:
46 | ASAN_OPTIONS: detect_leaks=0
47 | - name: check
48 | run: |
49 | make -C _build -j $(getconf _NPROCESSORS_ONLN) check VERBOSE=1 BWRAP_MUST_WORK=1
50 | env:
51 | ASAN_OPTIONS: detect_leaks=0
52 | - name: Collect overall test logs on failure
53 | if: failure()
54 | run: mv _build/test-suite.log test-logs/ || true
55 | - name: Collect individual test logs on cancel
56 | if: failure() || cancelled()
57 | run: mv _build/tests/*.log test-logs/ || true
58 | - name: Upload test logs
59 | uses: actions/upload-artifact@v1
60 | if: failure() || cancelled()
61 | with:
62 | name: test logs
63 | path: test-logs
64 | - name: install
65 | run: |
66 | make -C _build install DESTDIR="$(pwd)/DESTDIR"
67 | ( cd DESTDIR && find -ls )
68 | - name: distcheck
69 | run: |
70 | make -C _build -j $(getconf _NPROCESSORS_ONLN) distcheck VERBOSE=1 BWRAP_MUST_WORK=1
71 |
72 | meson:
73 | name: Build with Meson and gcc, and test
74 | runs-on: ubuntu-latest
75 | steps:
76 | - name: Check out
77 | uses: actions/checkout@v1
78 | - name: Install build-dependencies
79 | run: sudo ./ci/builddeps.sh
80 | - name: Create logs dir
81 | run: mkdir test-logs
82 | - name: setup
83 | run: |
84 | meson _build
85 | env:
86 | CFLAGS: >-
87 | -O2
88 | -Wp,-D_FORTIFY_SOURCE=2
89 | -fsanitize=address
90 | -fsanitize=undefined
91 | - name: compile
92 | run: ninja -C _build -v
93 | - name: smoke-test
94 | run: |
95 | set -x
96 | ./_build/bwrap --bind / / --tmpfs /tmp true
97 | env:
98 | ASAN_OPTIONS: detect_leaks=0
99 | - name: test
100 | run: |
101 | BWRAP_MUST_WORK=1 meson test -C _build
102 | env:
103 | ASAN_OPTIONS: detect_leaks=0
104 | - name: Collect overall test logs on failure
105 | if: failure()
106 | run: mv _build/meson-logs/testlog.txt test-logs/ || true
107 | - name: install
108 | run: |
109 | DESTDIR="$(pwd)/DESTDIR" meson install -C _build
110 | ( cd DESTDIR && find -ls )
111 | - name: dist
112 | run: |
113 | BWRAP_MUST_WORK=1 meson dist -C _build
114 | - name: Collect dist test logs on failure
115 | if: failure()
116 | run: mv _build/meson-private/dist-build/meson-logs/testlog.txt test-logs/disttestlog.txt || true
117 | - name: use as subproject
118 | run: |
119 | mkdir tests/use-as-subproject/subprojects
120 | tar -C tests/use-as-subproject/subprojects -xf _build/meson-dist/bubblewrap-*.tar.xz
121 | mv tests/use-as-subproject/subprojects/bubblewrap-* tests/use-as-subproject/subprojects/bubblewrap
122 | ( cd tests/use-as-subproject && meson _build )
123 | ninja -C tests/use-as-subproject/_build -v
124 | meson test -C tests/use-as-subproject/_build
125 | DESTDIR="$(pwd)/DESTDIR-as-subproject" meson install -C tests/use-as-subproject/_build
126 | ( cd DESTDIR-as-subproject && find -ls )
127 | test -x DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap
128 | test ! -e DESTDIR-as-subproject/usr/local/bin/bwrap
129 | test ! -e DESTDIR-as-subproject/usr/local/libexec/bwrap
130 | tests/use-as-subproject/assert-correct-rpath.py DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap
131 | - name: Upload test logs
132 | uses: actions/upload-artifact@v1
133 | if: failure() || cancelled()
134 | with:
135 | name: test logs
136 | path: test-logs
137 |
138 | clang:
139 | name: Build with clang and analyze
140 | runs-on: ubuntu-latest
141 | strategy:
142 | fail-fast: false
143 | matrix:
144 | language:
145 | - cpp
146 | steps:
147 | - name: Initialize CodeQL
148 | uses: github/codeql-action/init@v2
149 | with:
150 | languages: ${{ matrix.language }}
151 | - name: Check out
152 | uses: actions/checkout@v1
153 | - name: Install build-dependencies
154 | run: sudo ./ci/builddeps.sh --clang
155 | - name: autogen.sh
156 | run: NOCONFIGURE=1 ./autogen.sh
157 | - name: configure
158 | run: ./configure --enable-selinux
159 | env:
160 | CC: clang
161 | CFLAGS: >-
162 | -O2
163 | -Werror=unused-variable
164 | - name: make
165 | run: make -j $(getconf _NPROCESSORS_ONLN) V=1
166 | - name: CodeQL analysis
167 | uses: github/codeql-action/analyze@v2
168 |
--------------------------------------------------------------------------------
/configure.ac:
--------------------------------------------------------------------------------
1 | AC_PREREQ([2.63])
2 | AC_INIT([bubblewrap], [0.10.0], [atomic-devel@projectatomic.io])
3 | AC_CONFIG_HEADER([config.h])
4 | AC_CONFIG_MACRO_DIR([m4])
5 | AC_CONFIG_AUX_DIR([build-aux])
6 |
7 | AC_USE_SYSTEM_EXTENSIONS
8 |
9 | AM_INIT_AUTOMAKE([1.11 -Wno-portability foreign no-define tar-ustar no-dist-gzip dist-xz])
10 | AM_MAINTAINER_MODE([enable])
11 | AM_SILENT_RULES([yes])
12 |
13 | AC_SYS_LARGEFILE
14 |
15 | AC_PROG_CC
16 | AM_PROG_CC_C_O
17 | PKG_PROG_PKG_CONFIG([])
18 |
19 | AC_CHECK_HEADERS([sys/capability.h], [], [AC_MSG_ERROR([*** POSIX caps headers not found])])
20 |
21 | AC_ARG_ENABLE(man,
22 | [AS_HELP_STRING([--enable-man],
23 | [generate man pages [default=auto]])],,
24 | enable_man=maybe)
25 |
26 | AS_IF([test "$enable_man" != no], [
27 | AC_PATH_PROG([XSLTPROC], [xsltproc], [])
28 | AS_IF([test -z "$XSLTPROC"], [
29 | AS_IF([test "$enable_man" = yes], [
30 | AC_MSG_ERROR([xsltproc is required for --enable-man])
31 | ])
32 | enable_man=no
33 | ], [
34 | enable_man=yes
35 | ])
36 | ])
37 | AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no)
38 |
39 | AC_ARG_WITH([bash-completion-dir],
40 | AS_HELP_STRING([--with-bash-completion-dir[=PATH]],
41 | [Install the bash auto-completion script in this directory. @<:@default=yes@:>@]),
42 | [],
43 | [with_bash_completion_dir=yes])
44 |
45 | AS_IF([test "x$with_bash_completion_dir" = "xyes"],
46 | [
47 | PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
48 | [BASH_COMPLETION_DIR="`pkg-config --variable=completionsdir bash-completion`"],
49 | [BASH_COMPLETION_DIR="$datadir/bash-completion/completions"])
50 | ],
51 | [
52 | BASH_COMPLETION_DIR="$with_bash_completion_dir"
53 | ])
54 |
55 | AC_SUBST([BASH_COMPLETION_DIR])
56 | AM_CONDITIONAL([ENABLE_BASH_COMPLETION],[test "x$with_bash_completion_dir" != "xno"])
57 |
58 | AC_ARG_WITH([zsh-completion-dir],
59 | AS_HELP_STRING([--with-zsh-completion-dir[=PATH]],
60 | [Install the zsh auto-completion script in this directory. @<:@default=yes@:>@]),
61 | [],
62 | [with_zsh_completion_dir=yes])
63 |
64 | AS_IF([test "x$with_zsh_completion_dir" = "xyes"],
65 | [ZSH_COMPLETION_DIR="$datadir/zsh/site-functions"],
66 | [ZSH_COMPLETION_DIR="$with_zsh_completion_dir"])
67 |
68 |
69 | AC_SUBST([ZSH_COMPLETION_DIR])
70 | AM_CONDITIONAL([ENABLE_ZSH_COMPLETION], [test "x$with_zsh_completion_dir" != "xno"])
71 |
72 | # ------------------------------------------------------------------------------
73 | have_selinux=no
74 | AC_ARG_ENABLE(selinux, AS_HELP_STRING([--disable-selinux], [Disable optional SELINUX support]))
75 | AS_IF([test "x$enable_selinux" != "xno"], [
76 | PKG_CHECK_MODULES([SELINUX], [libselinux >= 2.1.9],
77 | [AC_DEFINE(HAVE_SELINUX, 1, [Define if SELinux is available])
78 | have_selinux=yes
79 | M4_DEFINES="$M4_DEFINES -DHAVE_SELINUX"],
80 | [have_selinux=no])
81 | AS_IF([test "x$have_selinux" = xno && test "x$enable_selinux" = xyes],
82 | [AC_MSG_ERROR([*** SELinux support requested but libraries not found])])
83 | PKG_CHECK_MODULES([SELINUX_2_3], [libselinux >= 2.3],
84 | [AC_DEFINE(HAVE_SELINUX_2_3, 1, [Define if SELinux is version >= 2.3])],
85 | [:])
86 | ])
87 | AM_CONDITIONAL(HAVE_SELINUX, [test "$have_selinux" = "yes"])
88 |
89 | dnl Keep this in sync with ostree, except remove -Werror=declaration-after-statement
90 | CC_CHECK_FLAGS_APPEND([WARN_CFLAGS], [CFLAGS], [\
91 | -pipe \
92 | -Wall \
93 | -Werror=shadow \
94 | -Werror=empty-body \
95 | -Werror=strict-prototypes \
96 | -Werror=missing-prototypes \
97 | -Werror=implicit-function-declaration \
98 | "-Werror=format=2 -Werror=format-security -Werror=format-nonliteral" \
99 | -Werror=pointer-arith -Werror=init-self \
100 | -Werror=missing-declarations \
101 | -Werror=return-type \
102 | -Werror=overflow \
103 | -Werror=int-conversion \
104 | -Werror=parenthesis \
105 | -Werror=incompatible-pointer-types \
106 | -Werror=misleading-indentation \
107 | -Werror=missing-include-dirs -Werror=aggregate-return \
108 | -Werror=switch-default \
109 | -Wswitch-enum \
110 | ])
111 | AC_SUBST(WARN_CFLAGS)
112 |
113 | AC_CHECK_LIB(cap, cap_from_text)
114 |
115 | AS_IF([test "$ac_cv_lib_cap_cap_from_text" != "yes"],
116 | [AC_MSG_ERROR([*** libcap requested but not found])])
117 |
118 | AC_ARG_WITH(priv-mode,
119 | AS_HELP_STRING([--with-priv-mode=setuid/none],
120 | [How to set privilege-raising during make install]),
121 | [],
122 | [with_priv_mode="none"])
123 |
124 | AM_CONDITIONAL(PRIV_MODE_SETUID, test "x$with_priv_mode" = "xsetuid")
125 |
126 | AC_ARG_ENABLE(sudo,
127 | AS_HELP_STRING([--enable-sudo],[Use sudo to set privileged mode on binaries during install (only needed if --with-priv-mode used)]),
128 | [SUDO_BIN="sudo"], [SUDO_BIN=""])
129 | AC_SUBST([SUDO_BIN])
130 |
131 | AC_ARG_ENABLE(require-userns,
132 | AS_HELP_STRING([--enable-require-userns=yes/no (default no)],
133 | [Require user namespaces by default when installed suid]),
134 | [],
135 | [enable_require_userns="no"])
136 |
137 | AS_IF([ test "x$enable_require_userns" = "xyes" ], [
138 | AC_DEFINE(ENABLE_REQUIRE_USERNS, 1, [Define if userns should be used by default in suid mode])
139 | ])
140 |
141 | AC_PROG_AWK
142 | AC_REQUIRE_AUX_FILE([tap-driver.sh])
143 |
144 | AC_CONFIG_FILES([
145 | Makefile
146 | ])
147 | AC_OUTPUT
148 |
149 | echo "
150 | bubblewrap $VERSION
151 | ===================
152 |
153 | man pages (xsltproc): $enable_man
154 | SELinux: $have_selinux
155 | setuid mode on make install: $with_priv_mode
156 | require default userns: $enable_require_userns
157 | mysteriously satisfying to pop: yes"
158 | echo ""
159 |
--------------------------------------------------------------------------------
/network.c:
--------------------------------------------------------------------------------
1 | /* bubblewrap
2 | * Copyright (C) 2016 Alexander Larsson
3 | * SPDX-License-Identifier: LGPL-2.0-or-later
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library. If not, see .
17 | *
18 | */
19 |
20 | #include "config.h"
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #include "utils.h"
30 | #include "network.h"
31 |
32 | static void *
33 | add_rta (struct nlmsghdr *header,
34 | int type,
35 | size_t size)
36 | {
37 | struct rtattr *rta;
38 | size_t rta_size = RTA_LENGTH (size);
39 |
40 | rta = (struct rtattr *) ((char *) header + NLMSG_ALIGN (header->nlmsg_len));
41 | rta->rta_type = type;
42 | rta->rta_len = rta_size;
43 |
44 | header->nlmsg_len = NLMSG_ALIGN (header->nlmsg_len) + rta_size;
45 |
46 | return RTA_DATA (rta);
47 | }
48 |
49 | static int
50 | rtnl_send_request (int rtnl_fd,
51 | struct nlmsghdr *header)
52 | {
53 | struct sockaddr_nl dst_addr = { AF_NETLINK, 0 };
54 | ssize_t sent;
55 |
56 | sent = sendto (rtnl_fd, (void *) header, header->nlmsg_len, 0,
57 | (struct sockaddr *) &dst_addr, sizeof (dst_addr));
58 | if (sent < 0)
59 | return -1;
60 |
61 | return 0;
62 | }
63 |
64 | static int
65 | rtnl_read_reply (int rtnl_fd,
66 | unsigned int seq_nr)
67 | {
68 | char buffer[1024];
69 | ssize_t received;
70 | struct nlmsghdr *rheader;
71 |
72 | while (1)
73 | {
74 | received = recv (rtnl_fd, buffer, sizeof (buffer), 0);
75 | if (received < 0)
76 | return -1;
77 |
78 | rheader = (struct nlmsghdr *) buffer;
79 | while (received >= NLMSG_HDRLEN)
80 | {
81 | if (rheader->nlmsg_seq != seq_nr)
82 | return -1;
83 | if ((pid_t)rheader->nlmsg_pid != getpid ())
84 | return -1;
85 | if (rheader->nlmsg_type == NLMSG_ERROR)
86 | {
87 | uint32_t *err = NLMSG_DATA (rheader);
88 | if (*err == 0)
89 | return 0;
90 |
91 | return -1;
92 | }
93 | if (rheader->nlmsg_type == NLMSG_DONE)
94 | return 0;
95 |
96 | rheader = NLMSG_NEXT (rheader, received);
97 | }
98 | }
99 | }
100 |
101 | static int
102 | rtnl_do_request (int rtnl_fd,
103 | struct nlmsghdr *header)
104 | {
105 | if (rtnl_send_request (rtnl_fd, header) != 0)
106 | return -1;
107 |
108 | if (rtnl_read_reply (rtnl_fd, header->nlmsg_seq) != 0)
109 | return -1;
110 |
111 | return 0;
112 | }
113 |
114 | static struct nlmsghdr *
115 | rtnl_setup_request (char *buffer,
116 | int type,
117 | int flags,
118 | size_t size)
119 | {
120 | struct nlmsghdr *header;
121 | size_t len = NLMSG_LENGTH (size);
122 | static uint32_t counter = 0;
123 |
124 | memset (buffer, 0, len);
125 |
126 | header = (struct nlmsghdr *) buffer;
127 | header->nlmsg_len = len;
128 | header->nlmsg_type = type;
129 | header->nlmsg_flags = flags | NLM_F_REQUEST;
130 | header->nlmsg_seq = counter++;
131 | header->nlmsg_pid = getpid ();
132 |
133 | return header;
134 | }
135 |
136 | void
137 | loopback_setup (void)
138 | {
139 | int r, if_loopback;
140 | cleanup_fd int rtnl_fd = -1;
141 | char buffer[1024];
142 | struct sockaddr_nl src_addr = { AF_NETLINK, 0 };
143 | struct nlmsghdr *header;
144 | struct ifaddrmsg *addmsg;
145 | struct ifinfomsg *infomsg;
146 | struct in_addr *ip_addr;
147 |
148 | src_addr.nl_pid = getpid ();
149 |
150 | if_loopback = (int) if_nametoindex ("lo");
151 | if (if_loopback <= 0)
152 | die_with_error ("loopback: Failed to look up lo");
153 |
154 | rtnl_fd = socket (PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
155 | if (rtnl_fd < 0)
156 | die_with_error ("loopback: Failed to create NETLINK_ROUTE socket");
157 |
158 | r = bind (rtnl_fd, (struct sockaddr *) &src_addr, sizeof (src_addr));
159 | if (r < 0)
160 | die_with_error ("loopback: Failed to bind NETLINK_ROUTE socket");
161 |
162 | header = rtnl_setup_request (buffer, RTM_NEWADDR,
163 | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK,
164 | sizeof (struct ifaddrmsg));
165 | addmsg = NLMSG_DATA (header);
166 |
167 | addmsg->ifa_family = AF_INET;
168 | addmsg->ifa_prefixlen = 8;
169 | addmsg->ifa_flags = IFA_F_PERMANENT;
170 | addmsg->ifa_scope = RT_SCOPE_HOST;
171 | addmsg->ifa_index = if_loopback;
172 |
173 | ip_addr = add_rta (header, IFA_LOCAL, sizeof (*ip_addr));
174 | ip_addr->s_addr = htonl (INADDR_LOOPBACK);
175 |
176 | ip_addr = add_rta (header, IFA_ADDRESS, sizeof (*ip_addr));
177 | ip_addr->s_addr = htonl (INADDR_LOOPBACK);
178 |
179 | assert (header->nlmsg_len < sizeof (buffer));
180 |
181 | if (rtnl_do_request (rtnl_fd, header) != 0)
182 | die_with_error ("loopback: Failed RTM_NEWADDR");
183 |
184 | header = rtnl_setup_request (buffer, RTM_NEWLINK,
185 | NLM_F_ACK,
186 | sizeof (struct ifinfomsg));
187 | infomsg = NLMSG_DATA (header);
188 |
189 | infomsg->ifi_family = AF_UNSPEC;
190 | infomsg->ifi_type = 0;
191 | infomsg->ifi_index = if_loopback;
192 | infomsg->ifi_flags = IFF_UP;
193 | infomsg->ifi_change = IFF_UP;
194 |
195 | assert (header->nlmsg_len < sizeof (buffer));
196 |
197 | if (rtnl_do_request (rtnl_fd, header) != 0)
198 | die_with_error ("loopback: Failed RTM_NEWLINK");
199 | }
200 |
--------------------------------------------------------------------------------
/utils.h:
--------------------------------------------------------------------------------
1 | /* bubblewrap
2 | * Copyright (C) 2016 Alexander Larsson
3 | * SPDX-License-Identifier: LGPL-2.0-or-later
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library. If not, see .
17 | *
18 | */
19 |
20 | #pragma once
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 |
34 | #if 0
35 | #define __debug__(x) printf x
36 | #else
37 | #define __debug__(x)
38 | #endif
39 |
40 | #define UNUSED __attribute__((__unused__))
41 |
42 | #define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0]))
43 |
44 | #define TRUE 1
45 | #define FALSE 0
46 | typedef int bool;
47 |
48 | #define PIPE_READ_END 0
49 | #define PIPE_WRITE_END 1
50 |
51 | #ifndef PR_SET_CHILD_SUBREAPER
52 | #define PR_SET_CHILD_SUBREAPER 36
53 | #endif
54 |
55 | void warn (const char *format,
56 | ...) __attribute__((format (printf, 1, 2)));
57 | void die_with_error (const char *format,
58 | ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2)));
59 | void die_with_mount_error (const char *format,
60 | ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2)));
61 | void die (const char *format,
62 | ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2)));
63 | void die_oom (void) __attribute__((__noreturn__));
64 | void die_unless_label_valid (const char *label);
65 |
66 | void fork_intermediate_child (void);
67 |
68 | void *xmalloc (size_t size);
69 | void *xcalloc (size_t nmemb, size_t size);
70 | void *xrealloc (void *ptr,
71 | size_t size);
72 | char *xstrdup (const char *str);
73 | void strfreev (char **str_array);
74 | void xclearenv (void);
75 | void xsetenv (const char *name,
76 | const char *value,
77 | int overwrite);
78 | void xunsetenv (const char *name);
79 | char *strconcat (const char *s1,
80 | const char *s2);
81 | char *strconcat3 (const char *s1,
82 | const char *s2,
83 | const char *s3);
84 | char * xasprintf (const char *format,
85 | ...) __attribute__((format (printf, 1, 2)));
86 | bool has_prefix (const char *str,
87 | const char *prefix);
88 | bool has_path_prefix (const char *str,
89 | const char *prefix);
90 | bool path_equal (const char *path1,
91 | const char *path2);
92 | int fdwalk (int proc_fd,
93 | int (*cb)(void *data,
94 | int fd),
95 | void *data);
96 | char *load_file_data (int fd,
97 | size_t *size);
98 | char *load_file_at (int dirfd,
99 | const char *path);
100 | int write_file_at (int dirfd,
101 | const char *path,
102 | const char *content);
103 | int write_to_fd (int fd,
104 | const char *content,
105 | ssize_t len);
106 | int copy_file_data (int sfd,
107 | int dfd);
108 | int copy_file (const char *src_path,
109 | const char *dst_path,
110 | mode_t mode);
111 | int create_file (const char *path,
112 | mode_t mode,
113 | const char *content);
114 | int ensure_file (const char *path,
115 | mode_t mode);
116 | int ensure_dir (const char *path,
117 | mode_t mode);
118 | int get_file_mode (const char *pathname);
119 | int mkdir_with_parents (const char *pathname,
120 | mode_t mode,
121 | bool create_last);
122 | void create_pid_socketpair (int sockets[2]);
123 | void send_pid_on_socket (int socket);
124 | int read_pid_from_socket (int socket);
125 | char *get_oldroot_path (const char *path);
126 | char *get_newroot_path (const char *path);
127 | char *readlink_malloc (const char *pathname);
128 |
129 | /* syscall wrappers */
130 | int raw_clone (unsigned long flags,
131 | void *child_stack);
132 | int pivot_root (const char *new_root,
133 | const char *put_old);
134 | char *label_mount (const char *opt,
135 | const char *mount_label);
136 | int label_exec (const char *exec_label);
137 | int label_create_file (const char *file_label);
138 |
139 | const char *mount_strerror (int errsv);
140 |
141 | static inline void
142 | cleanup_freep (void *p)
143 | {
144 | void **pp = (void **) p;
145 |
146 | if (*pp)
147 | free (*pp);
148 | }
149 |
150 | static inline void
151 | cleanup_strvp (void *p)
152 | {
153 | void **pp = (void **) p;
154 |
155 | strfreev (*pp);
156 | }
157 |
158 | static inline void
159 | cleanup_fdp (int *fdp)
160 | {
161 | int fd;
162 |
163 | assert (fdp);
164 |
165 | fd = *fdp;
166 | if (fd != -1)
167 | (void) close (fd);
168 | }
169 |
170 | #define cleanup_free __attribute__((cleanup (cleanup_freep)))
171 | #define cleanup_fd __attribute__((cleanup (cleanup_fdp)))
172 | #define cleanup_strv __attribute__((cleanup (cleanup_strvp)))
173 |
174 | static inline void *
175 | steal_pointer (void *pp)
176 | {
177 | void **ptr = (void **) pp;
178 | void *ref;
179 |
180 | ref = *ptr;
181 | *ptr = NULL;
182 |
183 | return ref;
184 | }
185 |
186 | /* type safety */
187 | #define steal_pointer(pp) \
188 | (0 ? (*(pp)) : (steal_pointer) (pp))
189 |
--------------------------------------------------------------------------------
/tests/test-utils.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © 2019-2021 Collabora Ltd.
3 | *
4 | * SPDX-License-Identifier: LGPL-2.0-or-later
5 | *
6 | * This program is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | * Lesser General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Lesser General Public
17 | * License along with this library. If not, see .
18 | */
19 |
20 | #include
21 | #include
22 | #include
23 |
24 | #include "utils.h"
25 |
26 | /* A small implementation of TAP */
27 | static unsigned int test_number = 0;
28 |
29 | __attribute__((format(printf, 1, 2)))
30 | static void
31 | ok (const char *format, ...)
32 | {
33 | va_list ap;
34 |
35 | printf ("ok %u - ", ++test_number);
36 | va_start (ap, format);
37 | vprintf (format, ap);
38 | va_end (ap);
39 | printf ("\n");
40 | }
41 |
42 | /* for simplicity we always die immediately on failure */
43 | #define not_ok(fmt, ...) die (fmt, ## __VA_ARGS__)
44 |
45 | /* approximately GLib-compatible helper macros */
46 | #define g_test_message(fmt, ...) printf ("# " fmt "\n", ## __VA_ARGS__)
47 | #define g_assert_cmpstr(left_expr, op, right_expr) \
48 | do { \
49 | const char *left = (left_expr); \
50 | const char *right = (right_expr); \
51 | if (strcmp0 (left, right) op 0) \
52 | ok ("%s (\"%s\") %s %s (\"%s\")", #left_expr, left, #op, #right_expr, right); \
53 | else \
54 | not_ok ("expected %s (\"%s\") %s %s (\"%s\")", \
55 | #left_expr, left, #op, #right_expr, right); \
56 | } while (0)
57 | #define g_assert_cmpint(left_expr, op, right_expr) \
58 | do { \
59 | intmax_t left = (left_expr); \
60 | intmax_t right = (right_expr); \
61 | if (left op right) \
62 | ok ("%s (%ji) %s %s (%ji)", #left_expr, left, #op, #right_expr, right); \
63 | else \
64 | not_ok ("expected %s (%ji) %s %s (%ji)", \
65 | #left_expr, left, #op, #right_expr, right); \
66 | } while (0)
67 | #define g_assert_cmpuint(left_expr, op, right_expr) \
68 | do { \
69 | uintmax_t left = (left_expr); \
70 | uintmax_t right = (right_expr); \
71 | if (left op right) \
72 | ok ("%s (%ju) %s %s (%ju)", #left_expr, left, #op, #right_expr, right); \
73 | else \
74 | not_ok ("expected %s (%ju) %s %s (%ju)", \
75 | #left_expr, left, #op, #right_expr, right); \
76 | } while (0)
77 | #define g_assert_true(expr) \
78 | do { \
79 | if ((expr)) \
80 | ok ("%s", #expr); \
81 | else \
82 | not_ok ("expected %s to be true", #expr); \
83 | } while (0)
84 | #define g_assert_false(expr) \
85 | do { \
86 | if (!(expr)) \
87 | ok ("!(%s)", #expr); \
88 | else \
89 | not_ok ("expected %s to be false", #expr); \
90 | } while (0)
91 | #define g_assert_null(expr) \
92 | do { \
93 | if ((expr) == NULL) \
94 | ok ("%s was null", #expr); \
95 | else \
96 | not_ok ("expected %s to be null", #expr); \
97 | } while (0)
98 | #define g_assert_nonnull(expr) \
99 | do { \
100 | if ((expr) != NULL) \
101 | ok ("%s wasn't null", #expr); \
102 | else \
103 | not_ok ("expected %s to be non-null", #expr); \
104 | } while (0)
105 |
106 | static int
107 | strcmp0 (const char *left,
108 | const char *right)
109 | {
110 | if (left == right)
111 | return 0;
112 |
113 | if (left == NULL)
114 | return -1;
115 |
116 | if (right == NULL)
117 | return 1;
118 |
119 | return strcmp (left, right);
120 | }
121 |
122 | static void
123 | test_n_elements (void)
124 | {
125 | int three[] = { 1, 2, 3 };
126 | g_assert_cmpuint (N_ELEMENTS (three), ==, 3);
127 | }
128 |
129 | static void
130 | test_strconcat (void)
131 | {
132 | const char *a = "aaa";
133 | const char *b = "bbb";
134 | char *ab = strconcat (a, b);
135 | g_assert_cmpstr (ab, ==, "aaabbb");
136 | free (ab);
137 | }
138 |
139 | static void
140 | test_strconcat3 (void)
141 | {
142 | const char *a = "aaa";
143 | const char *b = "bbb";
144 | const char *c = "ccc";
145 | char *abc = strconcat3 (a, b, c);
146 | g_assert_cmpstr (abc, ==, "aaabbbccc");
147 | free (abc);
148 | }
149 |
150 | static void
151 | test_has_prefix (void)
152 | {
153 | g_assert_true (has_prefix ("foo", "foo"));
154 | g_assert_true (has_prefix ("foobar", "foo"));
155 | g_assert_false (has_prefix ("foobar", "fool"));
156 | g_assert_false (has_prefix ("foo", "fool"));
157 | g_assert_true (has_prefix ("foo", ""));
158 | g_assert_true (has_prefix ("", ""));
159 | g_assert_false (has_prefix ("", "no"));
160 | g_assert_false (has_prefix ("yes", "no"));
161 | }
162 |
163 | static void
164 | test_has_path_prefix (void)
165 | {
166 | static const struct
167 | {
168 | const char *str;
169 | const char *prefix;
170 | bool expected;
171 | } tests[] =
172 | {
173 | { "/run/host/usr", "/run/host", TRUE },
174 | { "/run/host/usr", "/run/host/", TRUE },
175 | { "/run/host", "/run/host", TRUE },
176 | { "////run///host////usr", "//run//host", TRUE },
177 | { "////run///host////usr", "//run//host////", TRUE },
178 | { "/run/hostage", "/run/host", FALSE },
179 | /* Any number of leading slashes is ignored, even zero */
180 | { "foo/bar", "/foo", TRUE },
181 | { "/foo/bar", "foo", TRUE },
182 | };
183 | size_t i;
184 |
185 | for (i = 0; i < N_ELEMENTS (tests); i++)
186 | {
187 | const char *str = tests[i].str;
188 | const char *prefix = tests[i].prefix;
189 | bool expected = tests[i].expected;
190 |
191 | if (expected)
192 | g_test_message ("%s should have path prefix %s", str, prefix);
193 | else
194 | g_test_message ("%s should not have path prefix %s", str, prefix);
195 |
196 | if (expected)
197 | g_assert_true (has_path_prefix (str, prefix));
198 | else
199 | g_assert_false (has_path_prefix (str, prefix));
200 | }
201 | }
202 |
203 | int
204 | main (int argc UNUSED,
205 | char **argv UNUSED)
206 | {
207 | setvbuf (stdout, NULL, _IONBF, 0);
208 | test_n_elements ();
209 | test_strconcat ();
210 | test_strconcat3 ();
211 | test_has_prefix ();
212 | test_has_path_prefix ();
213 | printf ("1..%u\n", test_number);
214 | return 0;
215 | }
216 |
--------------------------------------------------------------------------------
/completions/zsh/_bwrap:
--------------------------------------------------------------------------------
1 | #compdef bwrap
2 |
3 | _bwrap_args_after_perms_size=(
4 | # Please sort alphabetically (in LC_ALL=C order) by option name
5 | '--tmpfs[Mount new tmpfs on DEST]:mount point for tmpfs:_files -/'
6 | )
7 |
8 | _bwrap_args_after_perms=(
9 | # Please sort alphabetically (in LC_ALL=C order) by option name
10 | '--bind-data[Copy from FD to file which is bind-mounted on DEST]: :_guard "[0-9]#" "file descriptor to read content":destination:_files'
11 | '--dir[Create dir at DEST]:directory to create:_files -/'
12 | '--file[Copy from FD to destination DEST]: :_guard "[0-9]#" "file descriptor to read content from":destination:_files'
13 | '--ro-bind-data[Copy from FD to file which is readonly bind-mounted on DEST]: :_guard "[0-9]#" "file descriptor to read content from":destination:_files'
14 | '--size[Set size in bytes for next action argument]: :->after_perms_size'
15 | '--tmpfs[Mount new tmpfs on DEST]:mount point for tmpfs:_files -/'
16 | )
17 |
18 | _bwrap_args_after_size=(
19 | # Please sort alphabetically (in LC_ALL=C order) by option name
20 | '--perms[Set permissions for next action argument]: :_guard "[0-7]#" "permissions in octal": :->after_perms_size'
21 | '--tmpfs[Mount new tmpfs on DEST]:mount point for tmpfs:_files -/'
22 | )
23 |
24 | _bwrap_args=(
25 | '*::arguments:_normal'
26 | $_bwrap_args_after_perms
27 |
28 | # Please sort alphabetically (in LC_ALL=C order) by option name
29 | '--add-seccomp-fd[Load and use seccomp rules from FD]: :_guard "[0-9]#" "file descriptor to read seccomp rules from"'
30 | '--assert-userns-disabled[Fail unless further use of user namespace inside sandbox is disabled]'
31 | '--args[Parse NUL-separated args from FD]: :_guard "[0-9]#" "file descriptor with NUL-separated arguments"'
32 | '--argv0[Set argv0 to the value VALUE before running the program]:value:'
33 | '--as-pid-1[Do not install a reaper process with PID=1]'
34 | '--bind-try[Equal to --bind but ignores non-existent SRC]:source:_files:destination:_files'
35 | '--bind[Bind mount the host path SRC on DEST]:source:_files:destination:_files'
36 | '--block-fd[Block on FD until some data to read is available]: :_guard "[0-9]#" "file descriptor to block on"'
37 | '--cap-add[Add cap CAP when running as privileged user]:capability to add:->caps'
38 | '--cap-drop[Drop cap CAP when running as privileged user]:capability to add:->caps'
39 | '--chdir[Change directory to DIR]:working directory for sandbox: _files -/'
40 | '--chmod[Set permissions]: :_guard "[0-7]#" "permissions in octal":path to set permissions:_files'
41 | '--clearenv[Unset all environment variables]'
42 | '--dev-bind-try[Equal to --dev-bind but ignores non-existent SRC]:source:_files:destination:_files'
43 | '--dev-bind[Bind mount the host path SRC on DEST, allowing device access]:source:_files:destination:_files'
44 | '--dev[Mount new dev on DEST]:mount point for /dev:_files -/'
45 | "--die-with-parent[Kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies.]"
46 | '--disable-userns[Disable further use of user namespaces inside sandbox]'
47 | '--exec-label[Exec label for the sandbox]:SELinux label:_selinux_contexts'
48 | '--file-label[File label for temporary sandbox content]:SELinux label:_selinux_contexts'
49 | '--gid[Custom gid in the sandbox (requires --unshare-user or --userns)]: :_guard "[0-9]#" "numeric group ID"'
50 | '--help[Print help and exit]'
51 | '--hostname[Custom hostname in the sandbox (requires --unshare-uts)]:hostname:'
52 | '--info-fd[Write information about the running container to FD]: :_guard "[0-9]#" "file descriptor to write to"'
53 | '--json-status-fd[Write container status to FD as multiple JSON documents]: :_guard "[0-9]#" "file descriptor to write to"'
54 | '--lock-file[Take a lock on DEST while sandbox is running]:lock file:_files'
55 | '--mqueue[Mount new mqueue on DEST]:mount point for mqueue:_files -/'
56 | '--new-session[Create a new terminal session]'
57 | '--perms[Set permissions for next action argument]: :_guard "[0-7]#" "permissions in octal": :->after_perms'
58 | '--pidns[Use this user namespace (as parent namespace if using --unshare-pid)]: :'
59 | '--proc[Mount new procfs on DEST]:mount point for procfs:_files -/'
60 | '--remount-ro[Remount DEST as readonly; does not recursively remount]:mount point to remount read-only:_files'
61 | '--ro-bind-try[Equal to --ro-bind but ignores non-existent SRC]:source:_files:destination:_files'
62 | '--ro-bind[Bind mount the host path SRC readonly on DEST]:source:_files:destination:_files'
63 | '--seccomp[Load and use seccomp rules from FD]: :_guard "[0-9]#" "file descriptor to read seccomp rules from"'
64 | '--setenv[Set an environment variable]:variable to set:_parameters -g "*export*":value of variable: :'
65 | '--size[Set size in bytes for next action argument]: :->after_size'
66 | '--symlink[Create symlink at DEST with target SRC]:symlink target:_files:symlink to create:_files:'
67 | '--sync-fd[Keep this fd open while sandbox is running]: :_guard "[0-9]#" "file descriptor to keep open"'
68 | '--uid[Custom uid in the sandbox (requires --unshare-user or --userns)]: :_guard "[0-9]#" "numeric group ID"'
69 | '(--clearenv)--unsetenv[Unset an environment variable]:variable to unset:_parameters -g "*export*"'
70 | '--unshare-all[Unshare every namespace we support by default]'
71 | '--unshare-cgroup-try[Create new cgroup namespace if possible else continue by skipping it]'
72 | '--unshare-cgroup[Create new cgroup namespace]'
73 | '--unshare-ipc[Create new ipc namespace]'
74 | '--unshare-net[Create new network namespace]'
75 | '--unshare-pid[Create new pid namespace]'
76 | '(--userns --userns2)--unshare-user[Create new user namespace (may be automatically implied if not setuid)]'
77 | '--unshare-user-try[Create new user namespace if possible else continue by skipping it]'
78 | '--unshare-uts[Create new uts namespace]'
79 | '(--unshare-user)--userns[Use this user namespace (cannot combine with --unshare-user)]: :'
80 | '--userns-block-fd[Block on FD until the user namespace is ready]: :_guard "[0-9]#" "file descriptor to block on"'
81 | '(--unshare-user)--userns2[After setup switch to this user namespace, only useful with --userns]: :'
82 | '--version[Print version]'
83 | )
84 |
85 | _bwrap() {
86 | _arguments -S $_bwrap_args
87 | case "$state" in
88 | after_perms)
89 | _values -S ' ' 'option' $_bwrap_args_after_perms
90 | ;;
91 |
92 | after_size)
93 | _values -S ' ' 'option' $_bwrap_args_after_size
94 | ;;
95 |
96 | after_perms_size)
97 | _values -S ' ' 'option' $_bwrap_args_after_perms_size
98 | ;;
99 |
100 | caps)
101 | # $ grep -E '#define\sCAP_\w+\s+[0-9]+' /usr/include/linux/capability.h | awk '{print $2}' | xargs echo
102 | local all_caps=(
103 | CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_FSETID \
104 | CAP_KILL CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_LINUX_IMMUTABLE \
105 | CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_ADMIN CAP_NET_RAW \
106 | CAP_IPC_LOCK CAP_IPC_OWNER CAP_SYS_MODULE CAP_SYS_RAWIO CAP_SYS_CHROOT \
107 | CAP_SYS_PTRACE CAP_SYS_PACCT CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_NICE \
108 | CAP_SYS_RESOURCE CAP_SYS_TIME CAP_SYS_TTY_CONFIG CAP_MKNOD CAP_LEASE \
109 | CAP_AUDIT_WRITE CAP_AUDIT_CONTROL CAP_SETFCAP CAP_MAC_OVERRIDE \
110 | CAP_MAC_ADMIN CAP_SYSLOG CAP_WAKE_ALARM CAP_BLOCK_SUSPEND CAP_AUDIT_READ
111 | )
112 | _values 'caps' $all_caps
113 | ;;
114 | esac
115 | }
116 |
--------------------------------------------------------------------------------
/m4/attributes.m4:
--------------------------------------------------------------------------------
1 | dnl Macros to check the presence of generic (non-typed) symbols.
2 | dnl Copyright (c) 2006-2008 Diego Pettenò
3 | dnl Copyright (c) 2006-2008 xine project
4 | dnl Copyright (c) 2012 Lucas De Marchi
5 | dnl
6 | dnl This program is free software; you can redistribute it and/or modify
7 | dnl it under the terms of the GNU General Public License as published by
8 | dnl the Free Software Foundation; either version 2, or (at your option)
9 | dnl any later version.
10 | dnl
11 | dnl This program is distributed in the hope that it will be useful,
12 | dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | dnl GNU General Public License for more details.
15 | dnl
16 | dnl You should have received a copy of the GNU General Public License
17 | dnl along with this program; if not, write to the Free Software
18 | dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 | dnl 02110-1301, USA.
20 | dnl
21 | dnl As a special exception, the copyright owners of the
22 | dnl macro gives unlimited permission to copy, distribute and modify the
23 | dnl configure scripts that are the output of Autoconf when processing the
24 | dnl Macro. You need not follow the terms of the GNU General Public
25 | dnl License when using or distributing such scripts, even though portions
26 | dnl of the text of the Macro appear in them. The GNU General Public
27 | dnl License (GPL) does govern all other use of the material that
28 | dnl constitutes the Autoconf Macro.
29 | dnl
30 | dnl This special exception to the GPL applies to versions of the
31 | dnl Autoconf Macro released by this project. When you make and
32 | dnl distribute a modified version of the Autoconf Macro, you may extend
33 | dnl this special exception to the GPL to apply to your modified version as
34 | dnl well.
35 |
36 | dnl Check if FLAG in ENV-VAR is supported by compiler and append it
37 | dnl to WHERE-TO-APPEND variable. Note that we invert -Wno-* checks to
38 | dnl -W* as gcc cannot test for negated warnings. If a C snippet is passed,
39 | dnl use it, otherwise use a simple main() definition that just returns 0.
40 | dnl CC_CHECK_FLAG_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG], [C-SNIPPET])
41 |
42 | AC_DEFUN([CC_CHECK_FLAG_APPEND], [
43 | AC_CACHE_CHECK([if $CC supports flag $3 in envvar $2],
44 | AS_TR_SH([cc_cv_$2_$3]),
45 | [eval "AS_TR_SH([cc_save_$2])='${$2}'"
46 | eval "AS_TR_SH([$2])='${cc_save_$2} -Werror `echo "$3" | sed 's/^-Wno-/-W/'`'"
47 | AC_LINK_IFELSE([AC_LANG_SOURCE(ifelse([$4], [],
48 | [int main(void) { return 0; } ],
49 | [$4]))],
50 | [eval "AS_TR_SH([cc_cv_$2_$3])='yes'"],
51 | [eval "AS_TR_SH([cc_cv_$2_$3])='no'"])
52 | eval "AS_TR_SH([$2])='$cc_save_$2'"])
53 |
54 | AS_IF([eval test x$]AS_TR_SH([cc_cv_$2_$3])[ = xyes],
55 | [eval "$1='${$1} $3'"])
56 | ])
57 |
58 | dnl CC_CHECK_FLAGS_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG1 FLAG2], [C-SNIPPET])
59 | AC_DEFUN([CC_CHECK_FLAGS_APPEND], [
60 | for flag in [$3]; do
61 | CC_CHECK_FLAG_APPEND([$1], [$2], $flag, [$4])
62 | done
63 | ])
64 |
65 | dnl Check if the flag is supported by linker (cacheable)
66 | dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
67 |
68 | AC_DEFUN([CC_CHECK_LDFLAGS], [
69 | AC_CACHE_CHECK([if $CC supports $1 flag],
70 | AS_TR_SH([cc_cv_ldflags_$1]),
71 | [ac_save_LDFLAGS="$LDFLAGS"
72 | LDFLAGS="$LDFLAGS $1"
73 | AC_LINK_IFELSE([int main() { return 1; }],
74 | [eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"],
75 | [eval "AS_TR_SH([cc_cv_ldflags_$1])="])
76 | LDFLAGS="$ac_save_LDFLAGS"
77 | ])
78 |
79 | AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes],
80 | [$2], [$3])
81 | ])
82 |
83 | dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for
84 | dnl the current linker to avoid undefined references in a shared object.
85 | AC_DEFUN([CC_NOUNDEFINED], [
86 | dnl We check $host for which systems to enable this for.
87 | AC_REQUIRE([AC_CANONICAL_HOST])
88 |
89 | case $host in
90 | dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads
91 | dnl are requested, as different implementations are present; to avoid problems
92 | dnl use -Wl,-z,defs only for those platform not behaving this way.
93 | *-freebsd* | *-openbsd*) ;;
94 | *)
95 | dnl First of all check for the --no-undefined variant of GNU ld. This allows
96 | dnl for a much more readable command line, so that people can understand what
97 | dnl it does without going to look for what the heck -z defs does.
98 | for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do
99 | CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"])
100 | break
101 | done
102 | ;;
103 | esac
104 |
105 | AC_SUBST([LDFLAGS_NOUNDEFINED])
106 | ])
107 |
108 | dnl Check for a -Werror flag or equivalent. -Werror is the GCC
109 | dnl and ICC flag that tells the compiler to treat all the warnings
110 | dnl as fatal. We usually need this option to make sure that some
111 | dnl constructs (like attributes) are not simply ignored.
112 | dnl
113 | dnl Other compilers don't support -Werror per se, but they support
114 | dnl an equivalent flag:
115 | dnl - Sun Studio compiler supports -errwarn=%all
116 | AC_DEFUN([CC_CHECK_WERROR], [
117 | AC_CACHE_CHECK(
118 | [for $CC way to treat warnings as errors],
119 | [cc_cv_werror],
120 | [CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror],
121 | [CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])])
122 | ])
123 | ])
124 |
125 | AC_DEFUN([CC_CHECK_ATTRIBUTE], [
126 | AC_REQUIRE([CC_CHECK_WERROR])
127 | AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))],
128 | AS_TR_SH([cc_cv_attribute_$1]),
129 | [ac_save_CFLAGS="$CFLAGS"
130 | CFLAGS="$CFLAGS $cc_cv_werror"
131 | AC_COMPILE_IFELSE([AC_LANG_SOURCE([$3])],
132 | [eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"],
133 | [eval "AS_TR_SH([cc_cv_attribute_$1])='no'"])
134 | CFLAGS="$ac_save_CFLAGS"
135 | ])
136 |
137 | AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes],
138 | [AC_DEFINE(
139 | AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1,
140 | [Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))]
141 | )
142 | $4],
143 | [$5])
144 | ])
145 |
146 | AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [
147 | CC_CHECK_ATTRIBUTE(
148 | [constructor],,
149 | [void __attribute__((constructor)) ctor() { int a; }],
150 | [$1], [$2])
151 | ])
152 |
153 | AC_DEFUN([CC_ATTRIBUTE_FORMAT], [
154 | CC_CHECK_ATTRIBUTE(
155 | [format], [format(printf, n, n)],
156 | [void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }],
157 | [$1], [$2])
158 | ])
159 |
160 | AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [
161 | CC_CHECK_ATTRIBUTE(
162 | [format_arg], [format_arg(printf)],
163 | [char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }],
164 | [$1], [$2])
165 | ])
166 |
167 | AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [
168 | CC_CHECK_ATTRIBUTE(
169 | [visibility_$1], [visibility("$1")],
170 | [void __attribute__((visibility("$1"))) $1_function() { }],
171 | [$2], [$3])
172 | ])
173 |
174 | AC_DEFUN([CC_ATTRIBUTE_NONNULL], [
175 | CC_CHECK_ATTRIBUTE(
176 | [nonnull], [nonnull()],
177 | [void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }],
178 | [$1], [$2])
179 | ])
180 |
181 | AC_DEFUN([CC_ATTRIBUTE_UNUSED], [
182 | CC_CHECK_ATTRIBUTE(
183 | [unused], ,
184 | [void some_function(void *foo, __attribute__((unused)) void *bar);],
185 | [$1], [$2])
186 | ])
187 |
188 | AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [
189 | CC_CHECK_ATTRIBUTE(
190 | [sentinel], ,
191 | [void some_function(void *foo, ...) __attribute__((sentinel));],
192 | [$1], [$2])
193 | ])
194 |
195 | AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [
196 | CC_CHECK_ATTRIBUTE(
197 | [deprecated], ,
198 | [void some_function(void *foo, ...) __attribute__((deprecated));],
199 | [$1], [$2])
200 | ])
201 |
202 | AC_DEFUN([CC_ATTRIBUTE_ALIAS], [
203 | CC_CHECK_ATTRIBUTE(
204 | [alias], [weak, alias],
205 | [void other_function(void *foo) { }
206 | void some_function(void *foo) __attribute__((weak, alias("other_function")));],
207 | [$1], [$2])
208 | ])
209 |
210 | AC_DEFUN([CC_ATTRIBUTE_MALLOC], [
211 | CC_CHECK_ATTRIBUTE(
212 | [malloc], ,
213 | [void * __attribute__((malloc)) my_alloc(int n);],
214 | [$1], [$2])
215 | ])
216 |
217 | AC_DEFUN([CC_ATTRIBUTE_PACKED], [
218 | CC_CHECK_ATTRIBUTE(
219 | [packed], ,
220 | [struct astructure { char a; int b; long c; void *d; } __attribute__((packed));],
221 | [$1], [$2])
222 | ])
223 |
224 | AC_DEFUN([CC_ATTRIBUTE_CONST], [
225 | CC_CHECK_ATTRIBUTE(
226 | [const], ,
227 | [int __attribute__((const)) twopow(int n) { return 1 << n; } ],
228 | [$1], [$2])
229 | ])
230 |
231 | AC_DEFUN([CC_FLAG_VISIBILITY], [
232 | AC_REQUIRE([CC_CHECK_WERROR])
233 | AC_CACHE_CHECK([if $CC supports -fvisibility=hidden],
234 | [cc_cv_flag_visibility],
235 | [cc_flag_visibility_save_CFLAGS="$CFLAGS"
236 | CFLAGS="$CFLAGS $cc_cv_werror"
237 | CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden],
238 | cc_cv_flag_visibility='yes',
239 | cc_cv_flag_visibility='no')
240 | CFLAGS="$cc_flag_visibility_save_CFLAGS"])
241 |
242 | AS_IF([test "x$cc_cv_flag_visibility" = "xyes"],
243 | [AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1,
244 | [Define this if the compiler supports the -fvisibility flag])
245 | $1],
246 | [$2])
247 | ])
248 |
249 | AC_DEFUN([CC_FUNC_EXPECT], [
250 | AC_REQUIRE([CC_CHECK_WERROR])
251 | AC_CACHE_CHECK([if compiler has __builtin_expect function],
252 | [cc_cv_func_expect],
253 | [ac_save_CFLAGS="$CFLAGS"
254 | CFLAGS="$CFLAGS $cc_cv_werror"
255 | AC_COMPILE_IFELSE([AC_LANG_SOURCE(
256 | [int some_function() {
257 | int a = 3;
258 | return (int)__builtin_expect(a, 3);
259 | }])],
260 | [cc_cv_func_expect=yes],
261 | [cc_cv_func_expect=no])
262 | CFLAGS="$ac_save_CFLAGS"
263 | ])
264 |
265 | AS_IF([test "x$cc_cv_func_expect" = "xyes"],
266 | [AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1,
267 | [Define this if the compiler supports __builtin_expect() function])
268 | $1],
269 | [$2])
270 | ])
271 |
272 | AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [
273 | AC_REQUIRE([CC_CHECK_WERROR])
274 | AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported],
275 | [cc_cv_attribute_aligned],
276 | [ac_save_CFLAGS="$CFLAGS"
277 | CFLAGS="$CFLAGS $cc_cv_werror"
278 | for cc_attribute_align_try in 64 32 16 8 4 2; do
279 | AC_COMPILE_IFELSE([AC_LANG_SOURCE([
280 | int main() {
281 | static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0;
282 | return c;
283 | }])], [cc_cv_attribute_aligned=$cc_attribute_align_try; break])
284 | done
285 | CFLAGS="$ac_save_CFLAGS"
286 | ])
287 |
288 | if test "x$cc_cv_attribute_aligned" != "x"; then
289 | AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned],
290 | [Define the highest alignment supported])
291 | fi
292 | ])
293 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Bubblewrap
2 | ==========
3 |
4 | Many container runtime tools like `systemd-nspawn`, `docker`,
5 | etc. focus on providing infrastructure for system administrators and
6 | orchestration tools (e.g. Kubernetes) to run containers.
7 |
8 | These tools are not suitable to give to unprivileged users, because it
9 | is trivial to turn such access into a fully privileged root shell
10 | on the host.
11 |
12 | User namespaces
13 | ---------------
14 |
15 | There is an effort in the Linux kernel called
16 | [user namespaces](https://www.google.com/search?q=user+namespaces+site%3Ahttps%3A%2F%2Flwn.net)
17 | which attempts to allow unprivileged users to use container features.
18 | While significant progress has been made, there are
19 | [still concerns](https://lwn.net/Articles/673597/) about it, and
20 | it is not available to unprivileged users in several production distributions
21 | such as CentOS/Red Hat Enterprise Linux 7, Debian Jessie, etc.
22 |
23 | See for example
24 | [CVE-2016-3135](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-3135)
25 | which is a local root vulnerability introduced by userns.
26 | [This March 2016 post](https://lkml.org/lkml/2016/3/9/555) has some
27 | more discussion.
28 |
29 | Bubblewrap could be viewed as setuid implementation of a *subset* of
30 | user namespaces. Emphasis on subset - specifically relevant to the
31 | above CVE, bubblewrap does not allow control over iptables.
32 |
33 | The original bubblewrap code existed before user namespaces - it inherits code from
34 | [xdg-app helper](https://cgit.freedesktop.org/xdg-app/xdg-app/tree/common/xdg-app-helper.c?id=4c3bf179e2e4a2a298cd1db1d045adaf3f564532)
35 | which in turn distantly derives from
36 | [linux-user-chroot](https://git.gnome.org/browse/linux-user-chroot).
37 |
38 | System security
39 | ---------------
40 |
41 | The maintainers of this tool believe that it does not, even when used
42 | in combination with typical software installed on that distribution,
43 | allow privilege escalation. It may increase the ability of a logged
44 | in user to perform denial of service attacks, however.
45 |
46 | In particular, bubblewrap uses `PR_SET_NO_NEW_PRIVS` to turn off
47 | setuid binaries, which is the [traditional way](https://en.wikipedia.org/wiki/Chroot#Limitations) to get out of things
48 | like chroots.
49 |
50 | Sandbox security
51 | ----------------
52 |
53 | bubblewrap is a tool for constructing sandbox environments.
54 | bubblewrap is not a complete, ready-made sandbox with a specific security
55 | policy.
56 |
57 | Some of bubblewrap's use-cases want a security boundary between the sandbox
58 | and the real system; other use-cases want the ability to change the layout of
59 | the filesystem for processes inside the sandbox, but do not aim to be a
60 | security boundary.
61 | As a result, the level of protection between the sandboxed processes and
62 | the host system is entirely determined by the arguments passed to
63 | bubblewrap.
64 |
65 | Whatever program constructs the command-line arguments for bubblewrap
66 | (often a larger framework like Flatpak, libgnome-desktop, sandwine
67 | or an ad-hoc script) is responsible for defining its own security model,
68 | and choosing appropriate bubblewrap command-line arguments to implement
69 | that security model.
70 |
71 | Some aspects of sandbox security that require particular care are described
72 | in the [Limitations](#limitations) section below.
73 |
74 | Users
75 | -----
76 |
77 | This program can be shared by all container tools which perform
78 | non-root operation, such as:
79 |
80 | - [Flatpak](http://www.flatpak.org)
81 | - [rpm-ostree unprivileged](https://github.com/projectatomic/rpm-ostree/pull/209)
82 | - [bwrap-oci](https://github.com/projectatomic/bwrap-oci)
83 |
84 | We would also like to see this be available in Kubernetes/OpenShift
85 | clusters. Having the ability for unprivileged users to use container
86 | features would make it significantly easier to do interactive
87 | debugging scenarios and the like.
88 |
89 | Installation
90 | ------------
91 |
92 | bubblewrap is available in the package repositories of the most Linux distributions
93 | and can be installed from there.
94 |
95 | If you need to build bubblewrap from source, you can do this with meson or autotools.
96 |
97 | meson:
98 |
99 | ```
100 | meson _builddir
101 | meson compile -C _builddir
102 | meson test -C _builddir
103 | meson install -C _builddir
104 | ```
105 |
106 | autotools:
107 |
108 | ```
109 | ./autogen.sh
110 | make
111 | sudo make install
112 | ```
113 |
114 | Usage
115 | -----
116 |
117 | bubblewrap works by creating a new, completely empty, mount
118 | namespace where the root is on a tmpfs that is invisible from the
119 | host, and will be automatically cleaned up when the last process
120 | exits. You can then use commandline options to construct the root
121 | filesystem and process environment and command to run in the
122 | namespace.
123 |
124 | There's a larger [demo script](./demos/bubblewrap-shell.sh) in the
125 | source code, but here's a trimmed down version which runs
126 | a new shell reusing the host's `/usr`.
127 |
128 | ```
129 | bwrap \
130 | --ro-bind /usr /usr \
131 | --symlink usr/lib64 /lib64 \
132 | --proc /proc \
133 | --dev /dev \
134 | --unshare-pid \
135 | --new-session \
136 | bash
137 | ```
138 |
139 | This is an incomplete example, but useful for purposes of
140 | illustration. More often, rather than creating a container using the
141 | host's filesystem tree, you want to target a chroot. There, rather
142 | than creating the symlink `lib64 -> usr/lib64` in the tmpfs, you might
143 | have already created it in the target rootfs.
144 |
145 | Sandboxing
146 | ----------
147 |
148 | The goal of bubblewrap is to run an application in a sandbox, where it
149 | has restricted access to parts of the operating system or user data
150 | such as the home directory.
151 |
152 | bubblewrap always creates a new mount namespace, and the user can specify
153 | exactly what parts of the filesystem should be visible in the sandbox.
154 | Any such directories you specify mounted `nodev` by default, and can be made readonly.
155 |
156 | Additionally you can use these kernel features:
157 |
158 | User namespaces ([CLONE_NEWUSER](http://linux.die.net/man/2/clone)): This hides all but the current uid and gid from the
159 | sandbox. You can also change what the value of uid/gid should be in the sandbox.
160 |
161 | IPC namespaces ([CLONE_NEWIPC](http://linux.die.net/man/2/clone)): The sandbox will get its own copy of all the
162 | different forms of IPCs, like SysV shared memory and semaphores.
163 |
164 | 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/).
165 |
166 |
167 | 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.
168 |
169 | UTS namespace ([CLONE_NEWUTS](http://linux.die.net/man/2/clone)): The sandbox will have its own hostname.
170 |
171 | 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).
172 |
173 | Limitations
174 | -----------
175 |
176 | As noted in the [Sandbox security](#sandbox-security) section above,
177 | the level of protection between the sandboxed processes and the host system
178 | is entirely determined by the arguments passed to bubblewrap.
179 | Some aspects that require special care are noted here.
180 |
181 | - If you are not filtering out `TIOCSTI` commands using seccomp filters,
182 | argument `--new-session` is needed to protect against out-of-sandbox
183 | command execution
184 | (see [CVE-2017-5226](https://github.com/containers/bubblewrap/issues/142)).
185 |
186 | - Everything mounted into the sandbox can potentially be used to escalate
187 | privileges.
188 | For example, if you bind a D-Bus socket into the sandbox, it can be used to
189 | execute commands via systemd. You can use
190 | [xdg-dbus-proxy](https://github.com/flatpak/xdg-dbus-proxy) to filter
191 | D-Bus communication.
192 |
193 | - Some applications deploy their own sandboxing mechanisms, and these can be
194 | restricted by the constraints imposed by bubblewrap's sandboxing.
195 | For example, some web browsers which configure their child proccesses via
196 | seccomp to not have access to the filesystem. If you limit the syscalls and
197 | don't allow the seccomp syscall, a browser cannot apply these restrictions.
198 | Similarly, if these rules were compiled into a file that is not available in
199 | the sandbox, the browser cannot load these rules from this file and cannot
200 | apply these restrictions.
201 |
202 | Related project comparison: Firejail
203 | ------------------------------------
204 |
205 | [Firejail](https://github.com/netblue30/firejail/tree/HEAD/src/firejail)
206 | is similar to Flatpak before bubblewrap was split out in that it combines
207 | a setuid tool with a lot of desktop-specific sandboxing features. For
208 | example, Firejail knows about Pulseaudio, whereas bubblewrap does not.
209 |
210 | The bubblewrap authors believe it's much easier to audit a small
211 | setuid program, and keep features such as Pulseaudio filtering as an
212 | unprivileged process, as now occurs in Flatpak.
213 |
214 | Also, @cgwalters thinks trying to
215 | [whitelist file paths](https://github.com/netblue30/firejail/blob/37a5a3545ef6d8d03dad8bbd888f53e13274c9e5/src/firejail/fs_whitelist.c#L176)
216 | is a bad idea given the myriad ways users have to manipulate paths,
217 | and the myriad ways in which system administrators may configure a
218 | system. The bubblewrap approach is to only retain a few specific
219 | Linux capabilities such as `CAP_SYS_ADMIN`, but to always access the
220 | filesystem as the invoking uid. This entirely closes
221 | [TOCTTOU attacks](https://cwe.mitre.org/data/definitions/367.html) and
222 | such.
223 |
224 | Related project comparison: Sandstorm.io
225 | ----------------------------------------
226 |
227 | [Sandstorm.io](https://sandstorm.io/) requires unprivileged user
228 | namespaces to set up its sandbox, though it could easily be adapted
229 | to operate in a setuid mode as well. @cgwalters believes their code is
230 | fairly good, but it could still make sense to unify on bubblewrap.
231 | However, @kentonv (of Sandstorm) feels that while this makes sense
232 | in principle, the switching cost outweighs the practical benefits for
233 | now. This decision could be re-evaluated in the future, but it is not
234 | being actively pursued today.
235 |
236 | Related project comparison: runc/binctr
237 | ----------------------------------------
238 |
239 | [runC](https://github.com/opencontainers/runc) is currently working on
240 | supporting [rootless containers](https://github.com/opencontainers/runc/pull/774),
241 | without needing `setuid` or any other privileges during installation of
242 | runC (using unprivileged user namespaces rather than `setuid`),
243 | creation, and management of containers. However, the standard mode of
244 | using runC is similar to [systemd nspawn](https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html)
245 | in that it is tooling intended to be invoked by root.
246 |
247 | The bubblewrap authors believe that runc and systemd-nspawn are not
248 | designed to be made setuid, and are distant from supporting such a mode.
249 | However with rootless containers, runC will be able to fulfill certain usecases
250 | that bubblewrap supports (with the added benefit of being a standardised and
251 | complete OCI runtime).
252 |
253 | [binctr](https://github.com/jfrazelle/binctr) is just a wrapper for
254 | runC, so inherits all of its design tradeoffs.
255 |
256 | What's with the name?!
257 | ----------------------
258 |
259 | The name bubblewrap was chosen to convey that this
260 | tool runs as the parent of the application (so wraps it in some sense) and creates
261 | a protective layer (the sandbox) around it.
262 |
263 | 
264 |
265 | (Bubblewrap cat by [dancing_stupidity](https://www.flickr.com/photos/27549668@N03/))
266 |
--------------------------------------------------------------------------------
/git.mk:
--------------------------------------------------------------------------------
1 | # git.mk, a small Makefile to autogenerate .gitignore files
2 | # for autotools-based projects.
3 | #
4 | # Copyright 2009, Red Hat, Inc.
5 | # Copyright 2010,2011,2012,2013 Behdad Esfahbod
6 | # Written by Behdad Esfahbod
7 | #
8 | # Copying and distribution of this file, with or without modification,
9 | # is permitted in any medium without royalty provided the copyright
10 | # notice and this notice are preserved.
11 | #
12 | # The latest version of this file can be downloaded from:
13 | GIT_MK_URL = https://raw.githubusercontent.com/behdad/git.mk/master/git.mk
14 | #
15 | # Bugs, etc, should be reported upstream at:
16 | # https://github.com/behdad/git.mk
17 | #
18 | # To use in your project, import this file in your git repo's toplevel,
19 | # then do "make -f git.mk". This modifies all Makefile.am files in
20 | # your project to -include git.mk. Remember to add that line to new
21 | # Makefile.am files you create in your project, or just rerun the
22 | # "make -f git.mk".
23 | #
24 | # This enables automatic .gitignore generation. If you need to ignore
25 | # more files, add them to the GITIGNOREFILES variable in your Makefile.am.
26 | # But think twice before doing that. If a file has to be in .gitignore,
27 | # chances are very high that it's a generated file and should be in one
28 | # of MOSTLYCLEANFILES, CLEANFILES, DISTCLEANFILES, or MAINTAINERCLEANFILES.
29 | #
30 | # The only case that you need to manually add a file to GITIGNOREFILES is
31 | # when remove files in one of mostlyclean-local, clean-local, distclean-local,
32 | # or maintainer-clean-local make targets.
33 | #
34 | # Note that for files like editor backup, etc, there are better places to
35 | # ignore them. See "man gitignore".
36 | #
37 | # If "make maintainer-clean" removes the files but they are not recognized
38 | # by this script (that is, if "git status" shows untracked files still), send
39 | # me the output of "git status" as well as your Makefile.am and Makefile for
40 | # the directories involved and I'll diagnose.
41 | #
42 | # For a list of toplevel files that should be in MAINTAINERCLEANFILES, see
43 | # Makefile.am.sample in the git.mk git repo.
44 | #
45 | # Don't EXTRA_DIST this file. It is supposed to only live in git clones,
46 | # not tarballs. It serves no useful purpose in tarballs and clutters the
47 | # build dir.
48 | #
49 | # This file knows how to handle autoconf, automake, libtool, gtk-doc,
50 | # gnome-doc-utils, yelp.m4, mallard, intltool, gsettings, dejagnu, appdata,
51 | # appstream.
52 | #
53 | # This makefile provides the following targets:
54 | #
55 | # - all: "make all" will build all gitignore files.
56 | # - gitignore: makes all gitignore files in the current dir and subdirs.
57 | # - .gitignore: make gitignore file for the current dir.
58 | # - gitignore-recurse: makes all gitignore files in the subdirs.
59 | #
60 | # KNOWN ISSUES:
61 | #
62 | # - Recursive configure doesn't work as $(top_srcdir)/git.mk inside the
63 | # submodule doesn't find us. If you have configure.{in,ac} files in
64 | # subdirs, add a proxy git.mk file in those dirs that simply does:
65 | # "include $(top_srcdir)/../git.mk". Add more ..'s to your taste.
66 | # And add those files to git. See vte/gnome-pty-helper/git.mk for
67 | # example.
68 | #
69 |
70 |
71 |
72 | ###############################################################################
73 | # Variables user modules may want to add to toplevel MAINTAINERCLEANFILES:
74 | ###############################################################################
75 |
76 | #
77 | # Most autotools-using modules should be fine including this variable in their
78 | # toplevel MAINTAINERCLEANFILES:
79 | GITIGNORE_MAINTAINERCLEANFILES_TOPLEVEL = \
80 | $(srcdir)/aclocal.m4 \
81 | $(srcdir)/autoscan.log \
82 | $(srcdir)/configure.scan \
83 | `AUX_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_AUX_DIR:$$1' ./configure.ac); \
84 | test "x$$AUX_DIR" = "x$(srcdir)/" && AUX_DIR=$(srcdir); \
85 | for x in \
86 | ar-lib \
87 | compile \
88 | config.guess \
89 | config.sub \
90 | depcomp \
91 | install-sh \
92 | ltmain.sh \
93 | missing \
94 | mkinstalldirs \
95 | test-driver \
96 | ylwrap \
97 | ; do echo "$$AUX_DIR/$$x"; done` \
98 | `cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_HEADERS:$$1' ./configure.ac | \
99 | head -n 1 | while read f; do echo "$(srcdir)/$$f.in"; done`
100 | #
101 | # All modules should also be fine including the following variable, which
102 | # removes automake-generated Makefile.in files:
103 | GITIGNORE_MAINTAINERCLEANFILES_MAKEFILE_IN = \
104 | `cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_FILES:$$1' ./configure.ac | \
105 | while read f; do \
106 | case $$f in Makefile|*/Makefile) \
107 | test -f "$(srcdir)/$$f.am" && echo "$(srcdir)/$$f.in";; esac; \
108 | done`
109 | #
110 | # Modules that use libtool and use AC_CONFIG_MACRO_DIR() may also include this,
111 | # though it's harmless to include regardless.
112 | GITIGNORE_MAINTAINERCLEANFILES_M4_LIBTOOL = \
113 | `MACRO_DIR=$(srcdir)/$$(cd $(top_srcdir); $(AUTOCONF) --trace 'AC_CONFIG_MACRO_DIR:$$1' ./configure.ac); \
114 | if test "x$$MACRO_DIR" != "x$(srcdir)/"; then \
115 | for x in \
116 | libtool.m4 \
117 | ltoptions.m4 \
118 | ltsugar.m4 \
119 | ltversion.m4 \
120 | lt~obsolete.m4 \
121 | ; do echo "$$MACRO_DIR/$$x"; done; \
122 | fi`
123 |
124 |
125 |
126 | ###############################################################################
127 | # Default rule is to install ourselves in all Makefile.am files:
128 | ###############################################################################
129 |
130 | git-all: git-mk-install
131 |
132 | git-mk-install:
133 | @echo "Installing git makefile"
134 | @any_failed=; \
135 | find "`test -z "$(top_srcdir)" && echo . || echo "$(top_srcdir)"`" -name Makefile.am | while read x; do \
136 | if grep 'include .*/git.mk' $$x >/dev/null; then \
137 | echo "$$x already includes git.mk"; \
138 | else \
139 | failed=; \
140 | echo "Updating $$x"; \
141 | { cat $$x; \
142 | echo ''; \
143 | echo '-include $$(top_srcdir)/git.mk'; \
144 | } > $$x.tmp || failed=1; \
145 | if test x$$failed = x; then \
146 | mv $$x.tmp $$x || failed=1; \
147 | fi; \
148 | if test x$$failed = x; then : else \
149 | echo "Failed updating $$x"; >&2 \
150 | any_failed=1; \
151 | fi; \
152 | fi; done; test -z "$$any_failed"
153 |
154 | git-mk-update:
155 | wget $(GIT_MK_URL) -O $(top_srcdir)/git.mk
156 |
157 | .PHONY: git-all git-mk-install git-mk-update
158 |
159 |
160 |
161 | ###############################################################################
162 | # Actual .gitignore generation:
163 | ###############################################################################
164 |
165 | $(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk
166 | @echo "git.mk: Generating $@"
167 | @{ \
168 | if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \
169 | for x in \
170 | $(DOC_MODULE)-decl-list.txt \
171 | $(DOC_MODULE)-decl.txt \
172 | tmpl/$(DOC_MODULE)-unused.sgml \
173 | "tmpl/*.bak" \
174 | $(REPORT_FILES) \
175 | $(DOC_MODULE).pdf \
176 | xml html \
177 | ; do echo "/$$x"; done; \
178 | FLAVOR=$$(cd $(top_srcdir); $(AUTOCONF) --trace 'GTK_DOC_CHECK:$$2' ./configure.ac); \
179 | case $$FLAVOR in *no-tmpl*) echo /tmpl;; esac; \
180 | if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-types"; then \
181 | echo "/$(DOC_MODULE).types"; \
182 | fi; \
183 | if echo "$(SCAN_OPTIONS)" | grep -q "\-\-rebuild-sections"; then \
184 | echo "/$(DOC_MODULE)-sections.txt"; \
185 | fi; \
186 | if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
187 | for x in \
188 | $(SETUP_FILES) \
189 | $(DOC_MODULE).types \
190 | ; do echo "/$$x"; done; \
191 | fi; \
192 | fi; \
193 | if test "x$(DOC_MODULE)$(DOC_ID)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \
194 | for lc in $(DOC_LINGUAS); do \
195 | for x in \
196 | $(if $(DOC_MODULE),$(DOC_MODULE).xml) \
197 | $(DOC_PAGES) \
198 | $(DOC_INCLUDES) \
199 | ; do echo "/$$lc/$$x"; done; \
200 | done; \
201 | for x in \
202 | $(_DOC_OMF_ALL) \
203 | $(_DOC_DSK_ALL) \
204 | $(_DOC_HTML_ALL) \
205 | $(_DOC_MOFILES) \
206 | $(DOC_H_FILE) \
207 | "*/.xml2po.mo" \
208 | "*/*.omf.out" \
209 | ; do echo /$$x; done; \
210 | fi; \
211 | if test "x$(HELP_ID)" = x -o "x$(HELP_LINGUAS)" = x; then :; else \
212 | for lc in $(HELP_LINGUAS); do \
213 | for x in \
214 | $(HELP_FILES) \
215 | "$$lc.stamp" \
216 | "$$lc.mo" \
217 | ; do echo "/$$lc/$$x"; done; \
218 | done; \
219 | fi; \
220 | if test "x$(gsettings_SCHEMAS)" = x; then :; else \
221 | for x in \
222 | $(gsettings_SCHEMAS:.xml=.valid) \
223 | $(gsettings__enum_file) \
224 | ; do echo "/$$x"; done; \
225 | fi; \
226 | if test "x$(appdata_XML)" = x; then :; else \
227 | for x in \
228 | $(appdata_XML:.xml=.valid) \
229 | ; do echo "/$$x"; done; \
230 | fi; \
231 | if test "x$(appstream_XML)" = x; then :; else \
232 | for x in \
233 | $(appstream_XML:.xml=.valid) \
234 | ; do echo "/$$x"; done; \
235 | fi; \
236 | if test -f $(srcdir)/po/Makefile.in.in; then \
237 | for x in \
238 | po/Makefile.in.in \
239 | po/Makefile.in.in~ \
240 | po/Makefile.in \
241 | po/Makefile \
242 | po/Makevars.template \
243 | po/POTFILES \
244 | po/Rules-quot \
245 | po/stamp-it \
246 | po/stamp-po \
247 | po/.intltool-merge-cache \
248 | "po/*.gmo" \
249 | "po/*.header" \
250 | "po/*.mo" \
251 | "po/*.sed" \
252 | "po/*.sin" \
253 | po/$(GETTEXT_PACKAGE).pot \
254 | intltool-extract.in \
255 | intltool-merge.in \
256 | intltool-update.in \
257 | ; do echo "/$$x"; done; \
258 | fi; \
259 | if test -f $(srcdir)/configure; then \
260 | for x in \
261 | autom4te.cache \
262 | configure \
263 | config.h \
264 | stamp-h1 \
265 | libtool \
266 | config.lt \
267 | ; do echo "/$$x"; done; \
268 | fi; \
269 | if test "x$(DEJATOOL)" = x; then :; else \
270 | for x in \
271 | $(DEJATOOL) \
272 | ; do echo "/$$x.sum"; echo "/$$x.log"; done; \
273 | echo /site.exp; \
274 | fi; \
275 | if test "x$(am__dirstamp)" = x; then :; else \
276 | echo "$(am__dirstamp)"; \
277 | fi; \
278 | if test "x$(LTCOMPILE)" = x -a "x$(LTCXXCOMPILE)" = x -a "x$(GTKDOC_RUN)" = x; then :; else \
279 | for x in \
280 | "*.lo" \
281 | ".libs" "_libs" \
282 | ; do echo "$$x"; done; \
283 | fi; \
284 | for x in \
285 | .gitignore \
286 | $(GITIGNOREFILES) \
287 | $(CLEANFILES) \
288 | $(PROGRAMS) $(check_PROGRAMS) $(EXTRA_PROGRAMS) \
289 | $(LIBRARIES) $(check_LIBRARIES) $(EXTRA_LIBRARIES) \
290 | $(LTLIBRARIES) $(check_LTLIBRARIES) $(EXTRA_LTLIBRARIES) \
291 | so_locations \
292 | $(MOSTLYCLEANFILES) \
293 | $(TEST_LOGS) \
294 | $(TEST_LOGS:.log=.trs) \
295 | $(TEST_SUITE_LOG) \
296 | $(TESTS:=.test) \
297 | "*.gcda" \
298 | "*.gcno" \
299 | $(DISTCLEANFILES) \
300 | $(am__CONFIG_DISTCLEAN_FILES) \
301 | $(CONFIG_CLEAN_FILES) \
302 | TAGS ID GTAGS GRTAGS GSYMS GPATH tags \
303 | "*.tab.c" \
304 | $(MAINTAINERCLEANFILES) \
305 | $(BUILT_SOURCES) \
306 | $(patsubst %.vala,%.c,$(filter %.vala,$(SOURCES))) \
307 | $(filter %_vala.stamp,$(DIST_COMMON)) \
308 | $(filter %.vapi,$(DIST_COMMON)) \
309 | $(filter $(addprefix %,$(notdir $(patsubst %.vapi,%.h,$(filter %.vapi,$(DIST_COMMON))))),$(DIST_COMMON)) \
310 | Makefile \
311 | Makefile.in \
312 | "*.orig" \
313 | "*.rej" \
314 | "*.bak" \
315 | "*~" \
316 | ".*.sw[nop]" \
317 | ".dirstamp" \
318 | ; do echo "/$$x"; done; \
319 | for x in \
320 | "*.$(OBJEXT)" \
321 | $(DEPDIR) \
322 | ; do echo "$$x"; done; \
323 | } | \
324 | sed "s@^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \
325 | sed 's@/[.]/@/@g' | \
326 | LC_ALL=C sort | uniq > $@.tmp && \
327 | mv $@.tmp $@;
328 |
329 | all: $(srcdir)/.gitignore gitignore-recurse-maybe
330 | gitignore: $(srcdir)/.gitignore gitignore-recurse
331 |
332 | gitignore-recurse-maybe:
333 | @for subdir in $(DIST_SUBDIRS); do \
334 | case " $(SUBDIRS) " in \
335 | *" $$subdir "*) :;; \
336 | *) test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir");; \
337 | esac; \
338 | done
339 | gitignore-recurse:
340 | @for subdir in $(DIST_SUBDIRS); do \
341 | test "$$subdir" = . -o -e "$$subdir/.git" || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) gitignore || echo "Skipping $$subdir"); \
342 | done
343 |
344 | maintainer-clean: gitignore-clean
345 | gitignore-clean:
346 | -rm -f $(srcdir)/.gitignore
347 |
348 | .PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe
349 |
--------------------------------------------------------------------------------
/bind-mount.c:
--------------------------------------------------------------------------------
1 | /* bubblewrap
2 | * Copyright (C) 2016 Alexander Larsson
3 | * SPDX-License-Identifier: LGPL-2.0-or-later
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library. If not, see .
17 | *
18 | */
19 |
20 | #include "config.h"
21 |
22 | #include
23 |
24 | #include "utils.h"
25 | #include "bind-mount.h"
26 |
27 | static char *
28 | skip_token (char *line, bool eat_whitespace)
29 | {
30 | while (*line != ' ' && *line != '\n')
31 | line++;
32 |
33 | if (eat_whitespace && *line == ' ')
34 | line++;
35 |
36 | return line;
37 | }
38 |
39 | static char *
40 | unescape_inline (char *escaped)
41 | {
42 | char *unescaped, *res;
43 | const char *end;
44 |
45 | res = escaped;
46 | end = escaped + strlen (escaped);
47 |
48 | unescaped = escaped;
49 | while (escaped < end)
50 | {
51 | if (*escaped == '\\')
52 | {
53 | *unescaped++ =
54 | ((escaped[1] - '0') << 6) |
55 | ((escaped[2] - '0') << 3) |
56 | ((escaped[3] - '0') << 0);
57 | escaped += 4;
58 | }
59 | else
60 | {
61 | *unescaped++ = *escaped++;
62 | }
63 | }
64 | *unescaped = 0;
65 | return res;
66 | }
67 |
68 | static bool
69 | match_token (const char *token, const char *token_end, const char *str)
70 | {
71 | while (token != token_end && *token == *str)
72 | {
73 | token++;
74 | str++;
75 | }
76 | if (token == token_end)
77 | return *str == 0;
78 |
79 | return FALSE;
80 | }
81 |
82 | static unsigned long
83 | decode_mountoptions (const char *options)
84 | {
85 | const char *token, *end_token;
86 | int i;
87 | unsigned long flags = 0;
88 | static const struct { int flag;
89 | const char *name;
90 | } flags_data[] = {
91 | { 0, "rw" },
92 | { MS_RDONLY, "ro" },
93 | { MS_NOSUID, "nosuid" },
94 | { MS_NODEV, "nodev" },
95 | { MS_NOEXEC, "noexec" },
96 | { MS_NOATIME, "noatime" },
97 | { MS_NODIRATIME, "nodiratime" },
98 | { MS_RELATIME, "relatime" },
99 | { 0, NULL }
100 | };
101 |
102 | token = options;
103 | do
104 | {
105 | end_token = strchr (token, ',');
106 | if (end_token == NULL)
107 | end_token = token + strlen (token);
108 |
109 | for (i = 0; flags_data[i].name != NULL; i++)
110 | {
111 | if (match_token (token, end_token, flags_data[i].name))
112 | {
113 | flags |= flags_data[i].flag;
114 | break;
115 | }
116 | }
117 |
118 | if (*end_token != 0)
119 | token = end_token + 1;
120 | else
121 | token = NULL;
122 | }
123 | while (token != NULL);
124 |
125 | return flags;
126 | }
127 |
128 | typedef struct MountInfo MountInfo;
129 | struct MountInfo {
130 | char *mountpoint;
131 | unsigned long options;
132 | };
133 |
134 | typedef MountInfo *MountTab;
135 |
136 | static void
137 | mount_tab_free (MountTab tab)
138 | {
139 | int i;
140 |
141 | for (i = 0; tab[i].mountpoint != NULL; i++)
142 | free (tab[i].mountpoint);
143 | free (tab);
144 | }
145 |
146 | static inline void
147 | cleanup_mount_tabp (void *p)
148 | {
149 | void **pp = (void **) p;
150 |
151 | if (*pp)
152 | mount_tab_free ((MountTab)*pp);
153 | }
154 |
155 | #define cleanup_mount_tab __attribute__((cleanup (cleanup_mount_tabp)))
156 |
157 | typedef struct MountInfoLine MountInfoLine;
158 | struct MountInfoLine {
159 | const char *mountpoint;
160 | const char *options;
161 | bool covered;
162 | int id;
163 | int parent_id;
164 | MountInfoLine *first_child;
165 | MountInfoLine *next_sibling;
166 | };
167 |
168 | static unsigned int
169 | count_lines (const char *data)
170 | {
171 | unsigned int count = 0;
172 | const char *p = data;
173 |
174 | while (*p != 0)
175 | {
176 | if (*p == '\n')
177 | count++;
178 | p++;
179 | }
180 |
181 | /* If missing final newline, add one */
182 | if (p > data && *(p-1) != '\n')
183 | count++;
184 |
185 | return count;
186 | }
187 |
188 | static int
189 | count_mounts (MountInfoLine *line)
190 | {
191 | MountInfoLine *child;
192 | int res = 0;
193 |
194 | if (!line->covered)
195 | res += 1;
196 |
197 | child = line->first_child;
198 | while (child != NULL)
199 | {
200 | res += count_mounts (child);
201 | child = child->next_sibling;
202 | }
203 |
204 | return res;
205 | }
206 |
207 | static MountInfo *
208 | collect_mounts (MountInfo *info, MountInfoLine *line)
209 | {
210 | MountInfoLine *child;
211 |
212 | if (!line->covered)
213 | {
214 | info->mountpoint = xstrdup (line->mountpoint);
215 | info->options = decode_mountoptions (line->options);
216 | info ++;
217 | }
218 |
219 | child = line->first_child;
220 | while (child != NULL)
221 | {
222 | info = collect_mounts (info, child);
223 | child = child->next_sibling;
224 | }
225 |
226 | return info;
227 | }
228 |
229 | static MountTab
230 | parse_mountinfo (int proc_fd,
231 | const char *root_mount)
232 | {
233 | cleanup_free char *mountinfo = NULL;
234 | cleanup_free MountInfoLine *lines = NULL;
235 | cleanup_free MountInfoLine **by_id = NULL;
236 | cleanup_mount_tab MountTab mount_tab = NULL;
237 | MountInfo *end_tab;
238 | int n_mounts;
239 | char *line;
240 | unsigned int i;
241 | int max_id;
242 | unsigned int n_lines;
243 | int root;
244 |
245 | mountinfo = load_file_at (proc_fd, "self/mountinfo");
246 | if (mountinfo == NULL)
247 | die_with_error ("Can't open /proc/self/mountinfo");
248 |
249 | n_lines = count_lines (mountinfo);
250 | lines = xcalloc (n_lines, sizeof (MountInfoLine));
251 |
252 | max_id = 0;
253 | line = mountinfo;
254 | i = 0;
255 | root = -1;
256 | while (*line != 0)
257 | {
258 | int rc, consumed = 0;
259 | unsigned int maj, min;
260 | char *end;
261 | char *rest;
262 | char *mountpoint;
263 | char *mountpoint_end;
264 | char *options;
265 | char *options_end;
266 | char *next_line;
267 |
268 | assert (i < n_lines);
269 |
270 | end = strchr (line, '\n');
271 | if (end != NULL)
272 | {
273 | *end = 0;
274 | next_line = end + 1;
275 | }
276 | else
277 | next_line = line + strlen (line);
278 |
279 | rc = sscanf (line, "%d %d %u:%u %n", &lines[i].id, &lines[i].parent_id, &maj, &min, &consumed);
280 | if (rc != 4)
281 | die ("Can't parse mountinfo line");
282 | rest = line + consumed;
283 |
284 | rest = skip_token (rest, TRUE); /* mountroot */
285 | mountpoint = rest;
286 | rest = skip_token (rest, FALSE); /* mountpoint */
287 | mountpoint_end = rest++;
288 | options = rest;
289 | rest = skip_token (rest, FALSE); /* vfs options */
290 | options_end = rest;
291 |
292 | *mountpoint_end = 0;
293 | lines[i].mountpoint = unescape_inline (mountpoint);
294 |
295 | *options_end = 0;
296 | lines[i].options = options;
297 |
298 | if (lines[i].id > max_id)
299 | max_id = lines[i].id;
300 | if (lines[i].parent_id > max_id)
301 | max_id = lines[i].parent_id;
302 |
303 | if (path_equal (lines[i].mountpoint, root_mount))
304 | root = i;
305 |
306 | i++;
307 | line = next_line;
308 | }
309 | assert (i == n_lines);
310 |
311 | if (root == -1)
312 | {
313 | mount_tab = xcalloc (1, sizeof (MountInfo));
314 | return steal_pointer (&mount_tab);
315 | }
316 |
317 | by_id = xcalloc (max_id + 1, sizeof (MountInfoLine*));
318 | for (i = 0; i < n_lines; i++)
319 | by_id[lines[i].id] = &lines[i];
320 |
321 | for (i = 0; i < n_lines; i++)
322 | {
323 | MountInfoLine *this = &lines[i];
324 | MountInfoLine *parent = by_id[this->parent_id];
325 | MountInfoLine **to_sibling;
326 | MountInfoLine *sibling;
327 | bool covered = FALSE;
328 |
329 | if (!has_path_prefix (this->mountpoint, root_mount))
330 | continue;
331 |
332 | if (parent == NULL)
333 | continue;
334 |
335 | if (strcmp (parent->mountpoint, this->mountpoint) == 0)
336 | parent->covered = TRUE;
337 |
338 | to_sibling = &parent->first_child;
339 | sibling = parent->first_child;
340 | while (sibling != NULL)
341 | {
342 | /* If this mountpoint is a path prefix of the sibling,
343 | * say this->mp=/foo/bar and sibling->mp=/foo, then it is
344 | * covered by the sibling, and we drop it. */
345 | if (has_path_prefix (this->mountpoint, sibling->mountpoint))
346 | {
347 | covered = TRUE;
348 | break;
349 | }
350 |
351 | /* If the sibling is a path prefix of this mount point,
352 | * say this->mp=/foo and sibling->mp=/foo/bar, then the sibling
353 | * is covered, and we drop it.
354 | */
355 | if (has_path_prefix (sibling->mountpoint, this->mountpoint))
356 | *to_sibling = sibling->next_sibling;
357 | else
358 | to_sibling = &sibling->next_sibling;
359 | sibling = sibling->next_sibling;
360 | }
361 |
362 | if (covered)
363 | continue;
364 |
365 | *to_sibling = this;
366 | }
367 |
368 | n_mounts = count_mounts (&lines[root]);
369 | mount_tab = xcalloc (n_mounts + 1, sizeof (MountInfo));
370 |
371 | end_tab = collect_mounts (&mount_tab[0], &lines[root]);
372 | assert (end_tab == &mount_tab[n_mounts]);
373 |
374 | return steal_pointer (&mount_tab);
375 | }
376 |
377 | bind_mount_result
378 | bind_mount (int proc_fd,
379 | const char *src,
380 | const char *dest,
381 | bind_option_t options,
382 | char **failing_path)
383 | {
384 | bool readonly = (options & BIND_READONLY) != 0;
385 | bool devices = (options & BIND_DEVICES) != 0;
386 | bool recursive = (options & BIND_RECURSIVE) != 0;
387 | unsigned long current_flags, new_flags;
388 | cleanup_mount_tab MountTab mount_tab = NULL;
389 | cleanup_free char *resolved_dest = NULL;
390 | cleanup_free char *dest_proc = NULL;
391 | cleanup_free char *oldroot_dest_proc = NULL;
392 | cleanup_free char *kernel_case_combination = NULL;
393 | cleanup_fd int dest_fd = -1;
394 | int i;
395 |
396 | if (src)
397 | {
398 | if (mount (src, dest, NULL, MS_SILENT | MS_BIND | (recursive ? MS_REC : 0), NULL) != 0)
399 | return BIND_MOUNT_ERROR_MOUNT;
400 | }
401 |
402 | /* The mount operation will resolve any symlinks in the destination
403 | path, so to find it in the mount table we need to do that too. */
404 | resolved_dest = realpath (dest, NULL);
405 | if (resolved_dest == NULL)
406 | return BIND_MOUNT_ERROR_REALPATH_DEST;
407 |
408 | dest_fd = open (resolved_dest, O_PATH | O_CLOEXEC);
409 | if (dest_fd < 0)
410 | {
411 | if (failing_path != NULL)
412 | *failing_path = steal_pointer (&resolved_dest);
413 |
414 | return BIND_MOUNT_ERROR_REOPEN_DEST;
415 | }
416 |
417 | /* If we are in a case-insensitive filesystem, mountinfo might contain a
418 | * different case combination of the path we requested to mount.
419 | * This is due to the fact that the kernel, as of the beginning of 2021,
420 | * populates mountinfo with whatever case combination first appeared in the
421 | * dcache; kernel developers plan to change this in future so that it
422 | * reflects the on-disk encoding instead.
423 | * To avoid throwing an error when this happens, we use readlink() result
424 | * instead of the provided @root_mount, so that we can compare the mountinfo
425 | * entries with the same case combination that the kernel is expected to
426 | * use. */
427 | dest_proc = xasprintf ("/proc/self/fd/%d", dest_fd);
428 | oldroot_dest_proc = get_oldroot_path (dest_proc);
429 | kernel_case_combination = readlink_malloc (oldroot_dest_proc);
430 | if (kernel_case_combination == NULL)
431 | {
432 | if (failing_path != NULL)
433 | *failing_path = steal_pointer (&resolved_dest);
434 |
435 | return BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD;
436 | }
437 |
438 | mount_tab = parse_mountinfo (proc_fd, kernel_case_combination);
439 | if (mount_tab[0].mountpoint == NULL)
440 | {
441 | if (failing_path != NULL)
442 | *failing_path = steal_pointer (&kernel_case_combination);
443 |
444 | errno = EINVAL;
445 | return BIND_MOUNT_ERROR_FIND_DEST_MOUNT;
446 | }
447 |
448 | assert (path_equal (mount_tab[0].mountpoint, kernel_case_combination));
449 | current_flags = mount_tab[0].options;
450 | new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0);
451 | if (new_flags != current_flags &&
452 | mount ("none", resolved_dest,
453 | NULL, MS_SILENT | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
454 | {
455 | if (failing_path != NULL)
456 | *failing_path = steal_pointer (&resolved_dest);
457 |
458 | return BIND_MOUNT_ERROR_REMOUNT_DEST;
459 | }
460 |
461 | /* We need to work around the fact that a bind mount does not apply the flags, so we need to manually
462 | * apply the flags to all submounts in the recursive case.
463 | * Note: This does not apply the flags to mounts which are later propagated into this namespace.
464 | */
465 | if (recursive)
466 | {
467 | for (i = 1; mount_tab[i].mountpoint != NULL; i++)
468 | {
469 | current_flags = mount_tab[i].options;
470 | new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0);
471 | if (new_flags != current_flags &&
472 | mount ("none", mount_tab[i].mountpoint,
473 | NULL, MS_SILENT | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
474 | {
475 | /* If we can't read the mountpoint we can't remount it, but that should
476 | be safe to ignore because its not something the user can access. */
477 | if (errno != EACCES)
478 | {
479 | if (failing_path != NULL)
480 | *failing_path = xstrdup (mount_tab[i].mountpoint);
481 |
482 | return BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT;
483 | }
484 | }
485 | }
486 | }
487 |
488 | return BIND_MOUNT_SUCCESS;
489 | }
490 |
491 | /**
492 | * Return a string representing bind_mount_result, like strerror().
493 | * If want_errno_p is non-NULL, *want_errno_p is used to indicate whether
494 | * it would make sense to print strerror(saved_errno).
495 | */
496 | static char *
497 | bind_mount_result_to_string (bind_mount_result res,
498 | const char *failing_path,
499 | bool *want_errno_p)
500 | {
501 | char *string = NULL;
502 | bool want_errno = TRUE;
503 |
504 | switch (res)
505 | {
506 | case BIND_MOUNT_ERROR_MOUNT:
507 | string = xstrdup ("Unable to mount source on destination");
508 | break;
509 |
510 | case BIND_MOUNT_ERROR_REALPATH_DEST:
511 | string = xstrdup ("realpath(destination)");
512 | break;
513 |
514 | case BIND_MOUNT_ERROR_REOPEN_DEST:
515 | string = xasprintf ("open(\"%s\", O_PATH)", failing_path);
516 | break;
517 |
518 | case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD:
519 | string = xasprintf ("readlink(/proc/self/fd/N) for \"%s\"", failing_path);
520 | break;
521 |
522 | case BIND_MOUNT_ERROR_FIND_DEST_MOUNT:
523 | string = xasprintf ("Unable to find \"%s\" in mount table", failing_path);
524 | want_errno = FALSE;
525 | break;
526 |
527 | case BIND_MOUNT_ERROR_REMOUNT_DEST:
528 | string = xasprintf ("Unable to remount destination \"%s\" with correct flags",
529 | failing_path);
530 | break;
531 |
532 | case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT:
533 | string = xasprintf ("Unable to apply mount flags: remount \"%s\"",
534 | failing_path);
535 | break;
536 |
537 | case BIND_MOUNT_SUCCESS:
538 | string = xstrdup ("Success");
539 | break;
540 |
541 | default:
542 | string = xstrdup ("(unknown/invalid bind_mount_result)");
543 | break;
544 | }
545 |
546 | if (want_errno_p != NULL)
547 | *want_errno_p = want_errno;
548 |
549 | return string;
550 | }
551 |
552 | void
553 | die_with_bind_result (bind_mount_result res,
554 | int saved_errno,
555 | const char *failing_path,
556 | const char *format,
557 | ...)
558 | {
559 | va_list args;
560 | bool want_errno = TRUE;
561 | char *message;
562 |
563 | fprintf (stderr, "bwrap: ");
564 |
565 | va_start (args, format);
566 | vfprintf (stderr, format, args);
567 | va_end (args);
568 |
569 | message = bind_mount_result_to_string (res, failing_path, &want_errno);
570 | fprintf (stderr, ": %s", message);
571 | /* message is leaked, but we're exiting unsuccessfully anyway, so ignore */
572 |
573 | if (want_errno)
574 | {
575 | switch (res)
576 | {
577 | case BIND_MOUNT_ERROR_MOUNT:
578 | case BIND_MOUNT_ERROR_REMOUNT_DEST:
579 | case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT:
580 | fprintf (stderr, ": %s", mount_strerror (saved_errno));
581 | break;
582 |
583 | case BIND_MOUNT_ERROR_REALPATH_DEST:
584 | case BIND_MOUNT_ERROR_REOPEN_DEST:
585 | case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD:
586 | case BIND_MOUNT_ERROR_FIND_DEST_MOUNT:
587 | case BIND_MOUNT_SUCCESS:
588 | default:
589 | fprintf (stderr, ": %s", strerror (saved_errno));
590 | }
591 | }
592 |
593 | fprintf (stderr, "\n");
594 | exit (1);
595 | }
596 |
--------------------------------------------------------------------------------
/tests/test-seccomp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Copyright 2021 Simon McVittie
3 | # SPDX-License-Identifier: LGPL-2.0-or-later
4 |
5 | import errno
6 | import logging
7 | import os
8 | import subprocess
9 | import sys
10 | import tempfile
11 | import termios
12 | import unittest
13 |
14 | try:
15 | import seccomp
16 | except ImportError:
17 | print('1..0 # SKIP cannot import seccomp Python module')
18 | sys.exit(0)
19 |
20 |
21 | # This is the @default set from systemd as of 2021-10-11
22 | DEFAULT_SET = set('''
23 | brk
24 | cacheflush
25 | clock_getres
26 | clock_getres_time64
27 | clock_gettime
28 | clock_gettime64
29 | clock_nanosleep
30 | clock_nanosleep_time64
31 | execve
32 | exit
33 | exit_group
34 | futex
35 | futex_time64
36 | get_robust_list
37 | get_thread_area
38 | getegid
39 | getegid32
40 | geteuid
41 | geteuid32
42 | getgid
43 | getgid32
44 | getgroups
45 | getgroups32
46 | getpgid
47 | getpgrp
48 | getpid
49 | getppid
50 | getrandom
51 | getresgid
52 | getresgid32
53 | getresuid
54 | getresuid32
55 | getrlimit
56 | getsid
57 | gettid
58 | gettimeofday
59 | getuid
60 | getuid32
61 | membarrier
62 | mmap
63 | mmap2
64 | munmap
65 | nanosleep
66 | pause
67 | prlimit64
68 | restart_syscall
69 | rseq
70 | rt_sigreturn
71 | sched_getaffinity
72 | sched_yield
73 | set_robust_list
74 | set_thread_area
75 | set_tid_address
76 | set_tls
77 | sigreturn
78 | time
79 | ugetrlimit
80 | '''.split())
81 |
82 | # This is the @basic-io set from systemd
83 | BASIC_IO_SET = set('''
84 | _llseek
85 | close
86 | close_range
87 | dup
88 | dup2
89 | dup3
90 | lseek
91 | pread64
92 | preadv
93 | preadv2
94 | pwrite64
95 | pwritev
96 | pwritev2
97 | read
98 | readv
99 | write
100 | writev
101 | '''.split())
102 |
103 | # This is the @filesystem-io set from systemd
104 | FILESYSTEM_SET = set('''
105 | access
106 | chdir
107 | chmod
108 | close
109 | creat
110 | faccessat
111 | faccessat2
112 | fallocate
113 | fchdir
114 | fchmod
115 | fchmodat
116 | fcntl
117 | fcntl64
118 | fgetxattr
119 | flistxattr
120 | fremovexattr
121 | fsetxattr
122 | fstat
123 | fstat64
124 | fstatat64
125 | fstatfs
126 | fstatfs64
127 | ftruncate
128 | ftruncate64
129 | futimesat
130 | getcwd
131 | getdents
132 | getdents64
133 | getxattr
134 | inotify_add_watch
135 | inotify_init
136 | inotify_init1
137 | inotify_rm_watch
138 | lgetxattr
139 | link
140 | linkat
141 | listxattr
142 | llistxattr
143 | lremovexattr
144 | lsetxattr
145 | lstat
146 | lstat64
147 | mkdir
148 | mkdirat
149 | mknod
150 | mknodat
151 | newfstatat
152 | oldfstat
153 | oldlstat
154 | oldstat
155 | open
156 | openat
157 | openat2
158 | readlink
159 | readlinkat
160 | removexattr
161 | rename
162 | renameat
163 | renameat2
164 | rmdir
165 | setxattr
166 | stat
167 | stat64
168 | statfs
169 | statfs64
170 | statx
171 | symlink
172 | symlinkat
173 | truncate
174 | truncate64
175 | unlink
176 | unlinkat
177 | utime
178 | utimensat
179 | utimensat_time64
180 | utimes
181 | '''.split())
182 |
183 | # Miscellaneous syscalls used during process startup, at least on x86_64
184 | ALLOWED = DEFAULT_SET | BASIC_IO_SET | FILESYSTEM_SET | set('''
185 | arch_prctl
186 | ioctl
187 | madvise
188 | mprotect
189 | mremap
190 | prctl
191 | readdir
192 | umask
193 | '''.split())
194 |
195 | # Syscalls we will try to use, expecting them to be either allowed or
196 | # blocked by our allow and/or deny lists
197 | TRY_SYSCALLS = [
198 | 'chmod',
199 | 'chroot',
200 | 'clone3',
201 | 'ioctl TIOCNOTTY',
202 | 'ioctl TIOCSTI CVE-2019-10063',
203 | 'ioctl TIOCSTI',
204 | 'listen',
205 | 'prctl',
206 | ]
207 |
208 |
209 | class Test(unittest.TestCase):
210 | def setUp(self) -> None:
211 | here = os.path.dirname(os.path.abspath(__file__))
212 |
213 | if 'G_TEST_SRCDIR' in os.environ:
214 | self.test_srcdir = os.getenv('G_TEST_SRCDIR') + '/tests'
215 | else:
216 | self.test_srcdir = here
217 |
218 | if 'G_TEST_BUILDDIR' in os.environ:
219 | self.test_builddir = os.getenv('G_TEST_BUILDDIR') + '/tests'
220 | else:
221 | self.test_builddir = here
222 |
223 | self.bwrap = os.getenv('BWRAP', 'bwrap')
224 | self.try_syscall = os.path.join(self.test_builddir, 'try-syscall')
225 |
226 | completed = subprocess.run(
227 | [
228 | self.bwrap,
229 | '--ro-bind', '/', '/',
230 | 'true',
231 | ],
232 | stdin=subprocess.DEVNULL,
233 | stdout=subprocess.DEVNULL,
234 | stderr=2,
235 | )
236 |
237 | if completed.returncode != 0:
238 | raise unittest.SkipTest(
239 | 'cannot run bwrap (does it need to be setuid?)'
240 | )
241 |
242 | def tearDown(self) -> None:
243 | pass
244 |
245 | def test_no_seccomp(self) -> None:
246 | for syscall in TRY_SYSCALLS:
247 | print('# {} without seccomp'.format(syscall))
248 | completed = subprocess.run(
249 | [
250 | self.bwrap,
251 | '--ro-bind', '/', '/',
252 | self.try_syscall, syscall,
253 | ],
254 | stdin=subprocess.DEVNULL,
255 | stdout=subprocess.DEVNULL,
256 | stderr=2,
257 | )
258 |
259 | if (
260 | syscall == 'ioctl TIOCSTI CVE-2019-10063'
261 | and completed.returncode == errno.ENOENT
262 | ):
263 | print('# Cannot test 64-bit syscall parameter on 32-bit')
264 | continue
265 |
266 | if syscall == 'clone3':
267 | # If the kernel supports it, we didn't block it so
268 | # it fails with EFAULT. If the kernel doesn't support it,
269 | # it'll fail with ENOSYS instead.
270 | self.assertIn(
271 | completed.returncode,
272 | (errno.ENOSYS, errno.EFAULT),
273 | )
274 | elif syscall.startswith('ioctl') or syscall == 'listen':
275 | self.assertEqual(completed.returncode, errno.EBADF)
276 | else:
277 | self.assertEqual(completed.returncode, errno.EFAULT)
278 |
279 | def test_seccomp_allowlist(self) -> None:
280 | with tempfile.TemporaryFile() as allowlist_temp:
281 | allowlist = seccomp.SyscallFilter(seccomp.ERRNO(errno.ENOSYS))
282 |
283 | if os.uname().machine == 'x86_64':
284 | # Allow Python and try-syscall to be different word sizes
285 | allowlist.add_arch(seccomp.Arch.X86)
286 |
287 | for syscall in ALLOWED:
288 | try:
289 | allowlist.add_rule(seccomp.ALLOW, syscall)
290 | except Exception as e:
291 | print('# Cannot add {} to allowlist: {!r}'.format(syscall, e))
292 |
293 | allowlist.export_bpf(allowlist_temp)
294 |
295 | for syscall in TRY_SYSCALLS:
296 | print('# allowlist vs. {}'.format(syscall))
297 | allowlist_temp.seek(0, os.SEEK_SET)
298 |
299 | completed = subprocess.run(
300 | [
301 | self.bwrap,
302 | '--ro-bind', '/', '/',
303 | '--seccomp', str(allowlist_temp.fileno()),
304 | self.try_syscall, syscall,
305 | ],
306 | pass_fds=(allowlist_temp.fileno(),),
307 | stdin=subprocess.DEVNULL,
308 | stdout=subprocess.DEVNULL,
309 | stderr=2,
310 | )
311 |
312 | if (
313 | syscall == 'ioctl TIOCSTI CVE-2019-10063'
314 | and completed.returncode == errno.ENOENT
315 | ):
316 | print('# Cannot test 64-bit syscall parameter on 32-bit')
317 | continue
318 |
319 | if syscall.startswith('ioctl'):
320 | # We allow this, so it is executed (and in this simple
321 | # example, immediately fails)
322 | self.assertEqual(completed.returncode, errno.EBADF)
323 | elif syscall in ('chroot', 'listen', 'clone3'):
324 | # We don't allow these, so they fail with ENOSYS.
325 | # clone3 might also be failing with ENOSYS because
326 | # the kernel genuinely doesn't support it.
327 | self.assertEqual(completed.returncode, errno.ENOSYS)
328 | else:
329 | # We allow this, so it is executed (and in this simple
330 | # example, immediately fails)
331 | self.assertEqual(completed.returncode, errno.EFAULT)
332 |
333 | def test_seccomp_denylist(self) -> None:
334 | with tempfile.TemporaryFile() as denylist_temp:
335 | denylist = seccomp.SyscallFilter(seccomp.ALLOW)
336 |
337 | if os.uname().machine == 'x86_64':
338 | # Allow Python and try-syscall to be different word sizes
339 | denylist.add_arch(seccomp.Arch.X86)
340 |
341 | # Using ECONNREFUSED here because it's unlikely that any of
342 | # these syscalls will legitimately fail with that code, so
343 | # if they fail like this, it will be as a result of seccomp.
344 | denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chmod')
345 | denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chroot')
346 | denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'prctl')
347 | denylist.add_rule(
348 | seccomp.ERRNO(errno.ECONNREFUSED), 'ioctl',
349 | seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI),
350 | )
351 |
352 | denylist.export_bpf(denylist_temp)
353 |
354 | for syscall in TRY_SYSCALLS:
355 | print('# denylist vs. {}'.format(syscall))
356 | denylist_temp.seek(0, os.SEEK_SET)
357 |
358 | completed = subprocess.run(
359 | [
360 | self.bwrap,
361 | '--ro-bind', '/', '/',
362 | '--seccomp', str(denylist_temp.fileno()),
363 | self.try_syscall, syscall,
364 | ],
365 | pass_fds=(denylist_temp.fileno(),),
366 | stdin=subprocess.DEVNULL,
367 | stdout=subprocess.DEVNULL,
368 | stderr=2,
369 | )
370 |
371 | if (
372 | syscall == 'ioctl TIOCSTI CVE-2019-10063'
373 | and completed.returncode == errno.ENOENT
374 | ):
375 | print('# Cannot test 64-bit syscall parameter on 32-bit')
376 | continue
377 |
378 | if syscall == 'clone3':
379 | # If the kernel supports it, we didn't block it so
380 | # it fails with EFAULT. If the kernel doesn't support it,
381 | # it'll fail with ENOSYS instead.
382 | self.assertIn(
383 | completed.returncode,
384 | (errno.ENOSYS, errno.EFAULT),
385 | )
386 | elif syscall in ('ioctl TIOCNOTTY', 'listen'):
387 | # Not on the denylist
388 | self.assertEqual(completed.returncode, errno.EBADF)
389 | else:
390 | # We blocked all of these
391 | self.assertEqual(completed.returncode, errno.ECONNREFUSED)
392 |
393 | def test_seccomp_stacked(self, allowlist_first=False) -> None:
394 | with tempfile.TemporaryFile(
395 | ) as allowlist_temp, tempfile.TemporaryFile(
396 | ) as denylist_temp:
397 | # This filter is a simplified version of what Flatpak wants
398 |
399 | allowlist = seccomp.SyscallFilter(seccomp.ERRNO(errno.ENOSYS))
400 | denylist = seccomp.SyscallFilter(seccomp.ALLOW)
401 |
402 | if os.uname().machine == 'x86_64':
403 | # Allow Python and try-syscall to be different word sizes
404 | allowlist.add_arch(seccomp.Arch.X86)
405 | denylist.add_arch(seccomp.Arch.X86)
406 |
407 | for syscall in ALLOWED:
408 | try:
409 | allowlist.add_rule(seccomp.ALLOW, syscall)
410 | except Exception as e:
411 | print('# Cannot add {} to allowlist: {!r}'.format(syscall, e))
412 |
413 | denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chmod')
414 | denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'chroot')
415 | denylist.add_rule(
416 | seccomp.ERRNO(errno.ECONNREFUSED), 'ioctl',
417 | seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI),
418 | )
419 |
420 | # All seccomp programs except the last must allow prctl(),
421 | # because otherwise we wouldn't be able to add the remaining
422 | # seccomp programs. We document that the last program can
423 | # block prctl, so test that.
424 | if allowlist_first:
425 | denylist.add_rule(seccomp.ERRNO(errno.ECONNREFUSED), 'prctl')
426 |
427 | allowlist.export_bpf(allowlist_temp)
428 | denylist.export_bpf(denylist_temp)
429 |
430 | for syscall in TRY_SYSCALLS:
431 | print('# stacked vs. {}'.format(syscall))
432 | allowlist_temp.seek(0, os.SEEK_SET)
433 | denylist_temp.seek(0, os.SEEK_SET)
434 |
435 | if allowlist_first:
436 | fds = [allowlist_temp.fileno(), denylist_temp.fileno()]
437 | else:
438 | fds = [denylist_temp.fileno(), allowlist_temp.fileno()]
439 |
440 | completed = subprocess.run(
441 | [
442 | self.bwrap,
443 | '--ro-bind', '/', '/',
444 | '--add-seccomp-fd', str(fds[0]),
445 | '--add-seccomp-fd', str(fds[1]),
446 | self.try_syscall, syscall,
447 | ],
448 | pass_fds=fds,
449 | stdin=subprocess.DEVNULL,
450 | stdout=subprocess.DEVNULL,
451 | stderr=2,
452 | )
453 |
454 | if (
455 | syscall == 'ioctl TIOCSTI CVE-2019-10063'
456 | and completed.returncode == errno.ENOENT
457 | ):
458 | print('# Cannot test 64-bit syscall parameter on 32-bit')
459 | continue
460 |
461 | if syscall == 'ioctl TIOCNOTTY':
462 | # Not denied by the denylist, and allowed by the allowlist
463 | self.assertEqual(completed.returncode, errno.EBADF)
464 | elif syscall in ('clone3', 'listen'):
465 | # We didn't deny these, so the denylist has no effect
466 | # and we fall back to the allowlist, which doesn't
467 | # include them either.
468 | # clone3 might also be failing with ENOSYS because
469 | # the kernel genuinely doesn't support it.
470 | self.assertEqual(completed.returncode, errno.ENOSYS)
471 | elif syscall == 'chroot':
472 | # This is denied by the denylist *and* not allowed by
473 | # the allowlist. The result depends which one we added
474 | # first: the most-recently-added filter "wins".
475 | if allowlist_first:
476 | self.assertEqual(
477 | completed.returncode,
478 | errno.ECONNREFUSED,
479 | )
480 | else:
481 | self.assertEqual(completed.returncode, errno.ENOSYS)
482 | elif syscall == 'prctl':
483 | # We can only put this on the denylist if the denylist
484 | # is the last to be added.
485 | if allowlist_first:
486 | self.assertEqual(
487 | completed.returncode,
488 | errno.ECONNREFUSED,
489 | )
490 | else:
491 | self.assertEqual(completed.returncode, errno.EFAULT)
492 | else:
493 | # chmod is allowed by the allowlist but blocked by the
494 | # denylist. Denying takes precedence over allowing,
495 | # regardless of order.
496 | self.assertEqual(completed.returncode, errno.ECONNREFUSED)
497 |
498 | def test_seccomp_stacked_allowlist_first(self) -> None:
499 | self.test_seccomp_stacked(allowlist_first=True)
500 |
501 | def test_seccomp_invalid(self) -> None:
502 | with tempfile.TemporaryFile(
503 | ) as allowlist_temp, tempfile.TemporaryFile(
504 | ) as denylist_temp:
505 | completed = subprocess.run(
506 | [
507 | self.bwrap,
508 | '--ro-bind', '/', '/',
509 | '--add-seccomp-fd', '-1',
510 | 'true',
511 | ],
512 | stdin=subprocess.DEVNULL,
513 | stdout=subprocess.DEVNULL,
514 | stderr=subprocess.PIPE,
515 | )
516 | self.assertIn(b'bwrap: Invalid fd: -1\n', completed.stderr)
517 | self.assertEqual(completed.returncode, 1)
518 |
519 | completed = subprocess.run(
520 | [
521 | self.bwrap,
522 | '--ro-bind', '/', '/',
523 | '--seccomp', '0a',
524 | 'true',
525 | ],
526 | stdin=subprocess.DEVNULL,
527 | stdout=subprocess.DEVNULL,
528 | stderr=subprocess.PIPE,
529 | )
530 | self.assertIn(b'bwrap: Invalid fd: 0a\n', completed.stderr)
531 | self.assertEqual(completed.returncode, 1)
532 |
533 | completed = subprocess.run(
534 | [
535 | self.bwrap,
536 | '--ro-bind', '/', '/',
537 | '--add-seccomp-fd', str(denylist_temp.fileno()),
538 | '--seccomp', str(allowlist_temp.fileno()),
539 | 'true',
540 | ],
541 | pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
542 | stdin=subprocess.DEVNULL,
543 | stdout=subprocess.DEVNULL,
544 | stderr=subprocess.PIPE,
545 | )
546 | self.assertIn(
547 | b'bwrap: --seccomp cannot be combined with --add-seccomp-fd\n',
548 | completed.stderr,
549 | )
550 | self.assertEqual(completed.returncode, 1)
551 |
552 | completed = subprocess.run(
553 | [
554 | self.bwrap,
555 | '--ro-bind', '/', '/',
556 | '--seccomp', str(allowlist_temp.fileno()),
557 | '--add-seccomp-fd', str(denylist_temp.fileno()),
558 | 'true',
559 | ],
560 | pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
561 | stdin=subprocess.DEVNULL,
562 | stdout=subprocess.DEVNULL,
563 | stderr=subprocess.PIPE,
564 | )
565 | self.assertIn(
566 | b'--add-seccomp-fd cannot be combined with --seccomp',
567 | completed.stderr,
568 | )
569 | self.assertEqual(completed.returncode, 1)
570 |
571 | completed = subprocess.run(
572 | [
573 | self.bwrap,
574 | '--ro-bind', '/', '/',
575 | '--add-seccomp-fd', str(allowlist_temp.fileno()),
576 | '--add-seccomp-fd', str(allowlist_temp.fileno()),
577 | 'true',
578 | ],
579 | pass_fds=(allowlist_temp.fileno(), allowlist_temp.fileno()),
580 | stdin=subprocess.DEVNULL,
581 | stdout=subprocess.DEVNULL,
582 | stderr=subprocess.PIPE,
583 | )
584 | self.assertIn(
585 | b"bwrap: Can't read seccomp data: ",
586 | completed.stderr,
587 | )
588 | self.assertEqual(completed.returncode, 1)
589 |
590 | allowlist_temp.write(b'\x01')
591 | allowlist_temp.seek(0, os.SEEK_SET)
592 | completed = subprocess.run(
593 | [
594 | self.bwrap,
595 | '--ro-bind', '/', '/',
596 | '--add-seccomp-fd', str(denylist_temp.fileno()),
597 | '--add-seccomp-fd', str(allowlist_temp.fileno()),
598 | 'true',
599 | ],
600 | pass_fds=(allowlist_temp.fileno(), denylist_temp.fileno()),
601 | stdin=subprocess.DEVNULL,
602 | stdout=subprocess.DEVNULL,
603 | stderr=subprocess.PIPE,
604 | )
605 | self.assertIn(
606 | b'bwrap: Invalid seccomp data, must be multiple of 8\n',
607 | completed.stderr,
608 | )
609 | self.assertEqual(completed.returncode, 1)
610 |
611 |
612 | def main():
613 | logging.basicConfig(level=logging.DEBUG)
614 |
615 | try:
616 | from tap.runner import TAPTestRunner
617 | except ImportError:
618 | TAPTestRunner = None # type: ignore
619 |
620 | if TAPTestRunner is not None:
621 | runner = TAPTestRunner()
622 | runner.set_stream(True)
623 | unittest.main(testRunner=runner)
624 | else:
625 | print('# tap.runner not available, using simple TAP output')
626 | print('1..1')
627 | program = unittest.main(exit=False)
628 | if program.result.wasSuccessful():
629 | print('ok 1 - %r' % program.result)
630 | else:
631 | print('not ok 1 - %r' % program.result)
632 | sys.exit(1)
633 |
634 | if __name__ == '__main__':
635 | main()
636 |
--------------------------------------------------------------------------------
/utils.c:
--------------------------------------------------------------------------------
1 | /* bubblewrap
2 | * Copyright (C) 2016 Alexander Larsson
3 | * SPDX-License-Identifier: LGPL-2.0-or-later
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library. If not, see .
17 | *
18 | */
19 | #include "config.h"
20 |
21 | #include "utils.h"
22 | #include
23 | #include
24 | #include
25 | #include
26 | #ifdef HAVE_SELINUX
27 | #include
28 | #endif
29 |
30 | #ifndef HAVE_SELINUX_2_3
31 | /* libselinux older than 2.3 weren't const-correct */
32 | #define setexeccon(x) setexeccon ((security_context_t) x)
33 | #define setfscreatecon(x) setfscreatecon ((security_context_t) x)
34 | #define security_check_context(x) security_check_context ((security_context_t) x)
35 | #endif
36 |
37 | __attribute__((format(printf, 1, 0))) static void
38 | warnv (const char *format,
39 | va_list args,
40 | const char *detail)
41 | {
42 | fprintf (stderr, "bwrap: ");
43 | vfprintf (stderr, format, args);
44 |
45 | if (detail != NULL)
46 | fprintf (stderr, ": %s", detail);
47 |
48 | fprintf (stderr, "\n");
49 | }
50 |
51 | void
52 | warn (const char *format, ...)
53 | {
54 | va_list args;
55 |
56 | va_start (args, format);
57 | warnv (format, args, NULL);
58 | va_end (args);
59 | }
60 |
61 | void
62 | die_with_error (const char *format, ...)
63 | {
64 | va_list args;
65 | int errsv;
66 |
67 | errsv = errno;
68 |
69 | va_start (args, format);
70 | warnv (format, args, strerror (errsv));
71 | va_end (args);
72 |
73 | exit (1);
74 | }
75 |
76 | void
77 | die_with_mount_error (const char *format, ...)
78 | {
79 | va_list args;
80 | int errsv;
81 |
82 | errsv = errno;
83 |
84 | va_start (args, format);
85 | warnv (format, args, mount_strerror (errsv));
86 | va_end (args);
87 |
88 | exit (1);
89 | }
90 |
91 | void
92 | die (const char *format, ...)
93 | {
94 | va_list args;
95 |
96 | va_start (args, format);
97 | warnv (format, args, NULL);
98 | va_end (args);
99 |
100 | exit (1);
101 | }
102 |
103 | void
104 | die_unless_label_valid (UNUSED const char *label)
105 | {
106 | #ifdef HAVE_SELINUX
107 | if (is_selinux_enabled () == 1)
108 | {
109 | if (security_check_context (label) < 0)
110 | die_with_error ("invalid label %s", label);
111 | return;
112 | }
113 | #endif
114 | die ("labeling not supported on this system");
115 | }
116 |
117 | void
118 | die_oom (void)
119 | {
120 | fputs ("Out of memory\n", stderr);
121 | exit (1);
122 | }
123 |
124 | /* Fork, return in child, exiting the previous parent */
125 | void
126 | fork_intermediate_child (void)
127 | {
128 | int pid = fork ();
129 | if (pid == -1)
130 | die_with_error ("Can't fork for --pidns");
131 |
132 | /* Parent is an process not needed */
133 | if (pid != 0)
134 | exit (0);
135 | }
136 |
137 | void *
138 | xmalloc (size_t size)
139 | {
140 | void *res = malloc (size);
141 |
142 | if (res == NULL)
143 | die_oom ();
144 | return res;
145 | }
146 |
147 | void *
148 | xcalloc (size_t nmemb, size_t size)
149 | {
150 | void *res = calloc (nmemb, size);
151 |
152 | if (res == NULL)
153 | die_oom ();
154 | return res;
155 | }
156 |
157 | void *
158 | xrealloc (void *ptr, size_t size)
159 | {
160 | void *res;
161 |
162 | assert (size != 0);
163 |
164 | res = realloc (ptr, size);
165 |
166 | if (res == NULL)
167 | die_oom ();
168 | return res;
169 | }
170 |
171 | char *
172 | xstrdup (const char *str)
173 | {
174 | char *res;
175 |
176 | assert (str != NULL);
177 |
178 | res = strdup (str);
179 | if (res == NULL)
180 | die_oom ();
181 |
182 | return res;
183 | }
184 |
185 | void
186 | strfreev (char **str_array)
187 | {
188 | if (str_array)
189 | {
190 | int i;
191 |
192 | for (i = 0; str_array[i] != NULL; i++)
193 | free (str_array[i]);
194 |
195 | free (str_array);
196 | }
197 | }
198 |
199 | /* Compares if str has a specific path prefix. This differs
200 | from a regular prefix in two ways. First of all there may
201 | be multiple slashes separating the path elements, and
202 | secondly, if a prefix is matched that has to be en entire
203 | path element. For instance /a/prefix matches /a/prefix/foo/bar,
204 | but not /a/prefixfoo/bar. */
205 | bool
206 | has_path_prefix (const char *str,
207 | const char *prefix)
208 | {
209 | while (TRUE)
210 | {
211 | /* Skip consecutive slashes to reach next path
212 | element */
213 | while (*str == '/')
214 | str++;
215 | while (*prefix == '/')
216 | prefix++;
217 |
218 | /* No more prefix path elements? Done! */
219 | if (*prefix == 0)
220 | return TRUE;
221 |
222 | /* Compare path element */
223 | while (*prefix != 0 && *prefix != '/')
224 | {
225 | if (*str != *prefix)
226 | return FALSE;
227 | str++;
228 | prefix++;
229 | }
230 |
231 | /* Matched prefix path element,
232 | must be entire str path element */
233 | if (*str != '/' && *str != 0)
234 | return FALSE;
235 | }
236 | }
237 |
238 | bool
239 | path_equal (const char *path1,
240 | const char *path2)
241 | {
242 | while (TRUE)
243 | {
244 | /* Skip consecutive slashes to reach next path
245 | element */
246 | while (*path1 == '/')
247 | path1++;
248 | while (*path2 == '/')
249 | path2++;
250 |
251 | /* No more prefix path elements? Done! */
252 | if (*path1 == 0 || *path2 == 0)
253 | return *path1 == 0 && *path2 == 0;
254 |
255 | /* Compare path element */
256 | while (*path1 != 0 && *path1 != '/')
257 | {
258 | if (*path1 != *path2)
259 | return FALSE;
260 | path1++;
261 | path2++;
262 | }
263 |
264 | /* Matched path1 path element, must be entire path element */
265 | if (*path2 != '/' && *path2 != 0)
266 | return FALSE;
267 | }
268 | }
269 |
270 |
271 | bool
272 | has_prefix (const char *str,
273 | const char *prefix)
274 | {
275 | return strncmp (str, prefix, strlen (prefix)) == 0;
276 | }
277 |
278 | void
279 | xclearenv (void)
280 | {
281 | if (clearenv () != 0)
282 | die_with_error ("clearenv failed");
283 | }
284 |
285 | void
286 | xsetenv (const char *name, const char *value, int overwrite)
287 | {
288 | if (setenv (name, value, overwrite))
289 | die ("setenv failed");
290 | }
291 |
292 | void
293 | xunsetenv (const char *name)
294 | {
295 | if (unsetenv (name))
296 | die ("unsetenv failed");
297 | }
298 |
299 | char *
300 | strconcat (const char *s1,
301 | const char *s2)
302 | {
303 | size_t len = 0;
304 | char *res;
305 |
306 | if (s1)
307 | len += strlen (s1);
308 | if (s2)
309 | len += strlen (s2);
310 |
311 | res = xmalloc (len + 1);
312 | *res = 0;
313 | if (s1)
314 | strcat (res, s1);
315 | if (s2)
316 | strcat (res, s2);
317 |
318 | return res;
319 | }
320 |
321 | char *
322 | strconcat3 (const char *s1,
323 | const char *s2,
324 | const char *s3)
325 | {
326 | size_t len = 0;
327 | char *res;
328 |
329 | if (s1)
330 | len += strlen (s1);
331 | if (s2)
332 | len += strlen (s2);
333 | if (s3)
334 | len += strlen (s3);
335 |
336 | res = xmalloc (len + 1);
337 | *res = 0;
338 | if (s1)
339 | strcat (res, s1);
340 | if (s2)
341 | strcat (res, s2);
342 | if (s3)
343 | strcat (res, s3);
344 |
345 | return res;
346 | }
347 |
348 | char *
349 | xasprintf (const char *format,
350 | ...)
351 | {
352 | char *buffer = NULL;
353 | va_list args;
354 |
355 | va_start (args, format);
356 | if (vasprintf (&buffer, format, args) == -1)
357 | die_oom ();
358 | va_end (args);
359 |
360 | return buffer;
361 | }
362 |
363 | int
364 | fdwalk (int proc_fd, int (*cb)(void *data,
365 | int fd), void *data)
366 | {
367 | int open_max;
368 | int fd;
369 | int dfd;
370 | int res = 0;
371 | DIR *d;
372 |
373 | dfd = openat (proc_fd, "self/fd", O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY);
374 | if (dfd == -1)
375 | return res;
376 |
377 | if ((d = fdopendir (dfd)))
378 | {
379 | struct dirent *de;
380 |
381 | while ((de = readdir (d)))
382 | {
383 | long l;
384 | char *e = NULL;
385 |
386 | if (de->d_name[0] == '.')
387 | continue;
388 |
389 | errno = 0;
390 | l = strtol (de->d_name, &e, 10);
391 | if (errno != 0 || !e || *e)
392 | continue;
393 |
394 | fd = (int) l;
395 |
396 | if ((long) fd != l)
397 | continue;
398 |
399 | if (fd == dirfd (d))
400 | continue;
401 |
402 | if ((res = cb (data, fd)) != 0)
403 | break;
404 | }
405 |
406 | closedir (d);
407 | return res;
408 | }
409 |
410 | open_max = sysconf (_SC_OPEN_MAX);
411 |
412 | for (fd = 0; fd < open_max; fd++)
413 | if ((res = cb (data, fd)) != 0)
414 | break;
415 |
416 | return res;
417 | }
418 |
419 | /* Sets errno on error (!= 0), ENOSPC on short write */
420 | int
421 | write_to_fd (int fd,
422 | const char *content,
423 | ssize_t len)
424 | {
425 | ssize_t res;
426 |
427 | while (len > 0)
428 | {
429 | res = write (fd, content, len);
430 | if (res < 0 && errno == EINTR)
431 | continue;
432 | if (res <= 0)
433 | {
434 | if (res == 0) /* Unexpected short write, should not happen when writing to a file */
435 | errno = ENOSPC;
436 | return -1;
437 | }
438 | len -= res;
439 | content += res;
440 | }
441 |
442 | return 0;
443 | }
444 |
445 | /* Sets errno on error (!= 0), ENOSPC on short write */
446 | int
447 | write_file_at (int dirfd,
448 | const char *path,
449 | const char *content)
450 | {
451 | int fd;
452 | bool res;
453 | int errsv;
454 |
455 | fd = openat (dirfd, path, O_RDWR | O_CLOEXEC, 0);
456 | if (fd == -1)
457 | return -1;
458 |
459 | res = 0;
460 | if (content)
461 | res = write_to_fd (fd, content, strlen (content));
462 |
463 | errsv = errno;
464 | close (fd);
465 | errno = errsv;
466 |
467 | return res;
468 | }
469 |
470 | /* Sets errno on error (!= 0), ENOSPC on short write */
471 | int
472 | create_file (const char *path,
473 | mode_t mode,
474 | const char *content)
475 | {
476 | int fd;
477 | int res;
478 | int errsv;
479 |
480 | fd = creat (path, mode);
481 | if (fd == -1)
482 | return -1;
483 |
484 | res = 0;
485 | if (content)
486 | res = write_to_fd (fd, content, strlen (content));
487 |
488 | errsv = errno;
489 | close (fd);
490 | errno = errsv;
491 |
492 | return res;
493 | }
494 |
495 | int
496 | ensure_file (const char *path,
497 | mode_t mode)
498 | {
499 | struct stat buf;
500 |
501 | /* We check this ahead of time, otherwise
502 | the create file will fail in the read-only
503 | case with EROFS instead of EEXIST.
504 |
505 | We're trying to set up a mount point for a non-directory, so any
506 | non-directory, non-symlink is acceptable - it doesn't necessarily
507 | have to be a regular file. */
508 | if (stat (path, &buf) == 0 &&
509 | !S_ISDIR (buf.st_mode) &&
510 | !S_ISLNK (buf.st_mode))
511 | return 0;
512 |
513 | if (create_file (path, mode, NULL) != 0 && errno != EEXIST)
514 | return -1;
515 |
516 | return 0;
517 | }
518 |
519 |
520 | #define BUFSIZE 8192
521 | /* Sets errno on error (!= 0), ENOSPC on short write */
522 | int
523 | copy_file_data (int sfd,
524 | int dfd)
525 | {
526 | char buffer[BUFSIZE];
527 | ssize_t bytes_read;
528 |
529 | while (TRUE)
530 | {
531 | bytes_read = read (sfd, buffer, BUFSIZE);
532 | if (bytes_read == -1)
533 | {
534 | if (errno == EINTR)
535 | continue;
536 |
537 | return -1;
538 | }
539 |
540 | if (bytes_read == 0)
541 | break;
542 |
543 | if (write_to_fd (dfd, buffer, bytes_read) != 0)
544 | return -1;
545 | }
546 |
547 | return 0;
548 | }
549 |
550 | /* Sets errno on error (!= 0), ENOSPC on short write */
551 | int
552 | copy_file (const char *src_path,
553 | const char *dst_path,
554 | mode_t mode)
555 | {
556 | int sfd;
557 | int dfd;
558 | int res;
559 | int errsv;
560 |
561 | sfd = open (src_path, O_CLOEXEC | O_RDONLY);
562 | if (sfd == -1)
563 | return -1;
564 |
565 | dfd = creat (dst_path, mode);
566 | if (dfd == -1)
567 | {
568 | errsv = errno;
569 | close (sfd);
570 | errno = errsv;
571 | return -1;
572 | }
573 |
574 | res = copy_file_data (sfd, dfd);
575 |
576 | errsv = errno;
577 | close (sfd);
578 | close (dfd);
579 | errno = errsv;
580 |
581 | return res;
582 | }
583 |
584 | /* Sets errno on error (== NULL),
585 | * Always ensures terminating zero */
586 | char *
587 | load_file_data (int fd,
588 | size_t *size)
589 | {
590 | cleanup_free char *data = NULL;
591 | ssize_t data_read;
592 | ssize_t data_len;
593 | ssize_t res;
594 |
595 | data_read = 0;
596 | data_len = 4080;
597 | data = xmalloc (data_len);
598 |
599 | do
600 | {
601 | if (data_len == data_read + 1)
602 | {
603 | if (data_len > SSIZE_MAX / 2)
604 | {
605 | errno = EFBIG;
606 | return NULL;
607 | }
608 |
609 | data_len *= 2;
610 | data = xrealloc (data, data_len);
611 | }
612 |
613 | do
614 | res = read (fd, data + data_read, data_len - data_read - 1);
615 | while (res < 0 && errno == EINTR);
616 |
617 | if (res < 0)
618 | return NULL;
619 |
620 | data_read += res;
621 | }
622 | while (res > 0);
623 |
624 | data[data_read] = 0;
625 |
626 | if (size)
627 | *size = (size_t) data_read;
628 |
629 | return steal_pointer (&data);
630 | }
631 |
632 | /* Sets errno on error (== NULL),
633 | * Always ensures terminating zero */
634 | char *
635 | load_file_at (int dirfd,
636 | const char *path)
637 | {
638 | int fd;
639 | char *data;
640 | int errsv;
641 |
642 | fd = openat (dirfd, path, O_CLOEXEC | O_RDONLY);
643 | if (fd == -1)
644 | return NULL;
645 |
646 | data = load_file_data (fd, NULL);
647 |
648 | errsv = errno;
649 | close (fd);
650 | errno = errsv;
651 |
652 | return data;
653 | }
654 |
655 | /* Sets errno on error (< 0) */
656 | int
657 | get_file_mode (const char *pathname)
658 | {
659 | struct stat buf;
660 |
661 | if (stat (pathname, &buf) != 0)
662 | return -1;
663 |
664 | return buf.st_mode & S_IFMT;
665 | }
666 |
667 | int
668 | ensure_dir (const char *path,
669 | mode_t mode)
670 | {
671 | struct stat buf;
672 |
673 | /* We check this ahead of time, otherwise
674 | the mkdir call can fail in the read-only
675 | case with EROFS instead of EEXIST on some
676 | filesystems (such as NFS) */
677 | if (stat (path, &buf) == 0)
678 | {
679 | if (!S_ISDIR (buf.st_mode))
680 | {
681 | errno = ENOTDIR;
682 | return -1;
683 | }
684 |
685 | return 0;
686 | }
687 |
688 | if (mkdir (path, mode) == -1 && errno != EEXIST)
689 | return -1;
690 |
691 | return 0;
692 | }
693 |
694 |
695 | /* Sets errno on error (!= 0) */
696 | int
697 | mkdir_with_parents (const char *pathname,
698 | mode_t mode,
699 | bool create_last)
700 | {
701 | cleanup_free char *fn = NULL;
702 | char *p;
703 |
704 | if (pathname == NULL || *pathname == '\0')
705 | {
706 | errno = EINVAL;
707 | return -1;
708 | }
709 |
710 | fn = xstrdup (pathname);
711 |
712 | p = fn;
713 | while (*p == '/')
714 | p++;
715 |
716 | do
717 | {
718 | while (*p && *p != '/')
719 | p++;
720 |
721 | if (!*p)
722 | p = NULL;
723 | else
724 | *p = '\0';
725 |
726 | if (!create_last && p == NULL)
727 | break;
728 |
729 | if (ensure_dir (fn, mode) != 0)
730 | return -1;
731 |
732 | if (p)
733 | {
734 | *p++ = '/';
735 | while (*p && *p == '/')
736 | p++;
737 | }
738 | }
739 | while (p);
740 |
741 | return 0;
742 | }
743 |
744 | /* Send an ucred with current pid/uid/gid over a socket, it can be
745 | read back with read_pid_from_socket(), and then the kernel has
746 | translated it between namespaces as needed. */
747 | void
748 | send_pid_on_socket (int socket)
749 | {
750 | char buf[1] = { 0 };
751 | struct msghdr msg = {};
752 | struct iovec iov = { buf, sizeof (buf) };
753 | const ssize_t control_len_snd = CMSG_SPACE(sizeof(struct ucred));
754 | char control_buf_snd[control_len_snd];
755 | struct cmsghdr *cmsg;
756 | struct ucred *cred;
757 |
758 | msg.msg_iov = &iov;
759 | msg.msg_iovlen = 1;
760 | msg.msg_control = control_buf_snd;
761 | msg.msg_controllen = control_len_snd;
762 |
763 | cmsg = CMSG_FIRSTHDR(&msg);
764 | cmsg->cmsg_level = SOL_SOCKET;
765 | cmsg->cmsg_type = SCM_CREDENTIALS;
766 | cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
767 | cred = (struct ucred *)CMSG_DATA(cmsg);
768 |
769 | cred->pid = getpid ();
770 | cred->uid = geteuid ();
771 | cred->gid = getegid ();
772 |
773 | if (sendmsg (socket, &msg, 0) < 0)
774 | die_with_error ("Can't send pid");
775 | }
776 |
777 | void
778 | create_pid_socketpair (int sockets[2])
779 | {
780 | int enable = 1;
781 |
782 | if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0)
783 | die_with_error ("Can't create intermediate pids socket");
784 |
785 | if (setsockopt (sockets[0], SOL_SOCKET, SO_PASSCRED, &enable, sizeof (enable)) < 0)
786 | die_with_error ("Can't set SO_PASSCRED");
787 | }
788 |
789 | int
790 | read_pid_from_socket (int socket)
791 | {
792 | char recv_buf[1] = { 0 };
793 | struct msghdr msg = {};
794 | struct iovec iov = { recv_buf, sizeof (recv_buf) };
795 | const ssize_t control_len_rcv = CMSG_SPACE(sizeof(struct ucred));
796 | char control_buf_rcv[control_len_rcv];
797 | struct cmsghdr* cmsg;
798 |
799 | msg.msg_iov = &iov;
800 | msg.msg_iovlen = 1;
801 | msg.msg_control = control_buf_rcv;
802 | msg.msg_controllen = control_len_rcv;
803 |
804 | if (recvmsg (socket, &msg, 0) < 0)
805 | die_with_error ("Can't read pid from socket");
806 |
807 | if (msg.msg_controllen <= 0)
808 | die ("Unexpected short read from pid socket");
809 |
810 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
811 | {
812 | const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0);
813 | if (cmsg->cmsg_level == SOL_SOCKET &&
814 | cmsg->cmsg_type == SCM_CREDENTIALS &&
815 | payload_len == sizeof(struct ucred))
816 | {
817 | struct ucred *cred = (struct ucred *)CMSG_DATA(cmsg);
818 | return cred->pid;
819 | }
820 | }
821 | die ("No pid returned on socket");
822 | }
823 |
824 | /* Sets errno on error (== NULL),
825 | * Always ensures terminating zero */
826 | char *
827 | readlink_malloc (const char *pathname)
828 | {
829 | size_t size = 50;
830 | ssize_t n;
831 | cleanup_free char *value = NULL;
832 |
833 | do
834 | {
835 | if (size > SIZE_MAX / 2)
836 | die ("Symbolic link target pathname too long");
837 | size *= 2;
838 | value = xrealloc (value, size);
839 | n = readlink (pathname, value, size - 1);
840 | if (n < 0)
841 | return NULL;
842 | }
843 | while (size - 2 < (size_t)n);
844 |
845 | value[n] = 0;
846 | return steal_pointer (&value);
847 | }
848 |
849 | char *
850 | get_oldroot_path (const char *path)
851 | {
852 | while (*path == '/')
853 | path++;
854 | return strconcat ("/oldroot/", path);
855 | }
856 |
857 | char *
858 | get_newroot_path (const char *path)
859 | {
860 | while (*path == '/')
861 | path++;
862 | return strconcat ("/newroot/", path);
863 | }
864 |
865 | int
866 | raw_clone (unsigned long flags,
867 | void *child_stack)
868 | {
869 | #if defined(__s390__) || defined(__CRIS__)
870 | /* On s390 and cris the order of the first and second arguments
871 | * of the raw clone() system call is reversed. */
872 | return (int) syscall (__NR_clone, child_stack, flags);
873 | #else
874 | return (int) syscall (__NR_clone, flags, child_stack);
875 | #endif
876 | }
877 |
878 | int
879 | pivot_root (const char * new_root, const char * put_old)
880 | {
881 | #ifdef __NR_pivot_root
882 | return syscall (__NR_pivot_root, new_root, put_old);
883 | #else
884 | errno = ENOSYS;
885 | return -1;
886 | #endif
887 | }
888 |
889 | char *
890 | label_mount (const char *opt, UNUSED const char *mount_label)
891 | {
892 | #ifdef HAVE_SELINUX
893 | if (mount_label)
894 | {
895 | if (opt)
896 | return xasprintf ("%s,context=\"%s\"", opt, mount_label);
897 | else
898 | return xasprintf ("context=\"%s\"", mount_label);
899 | }
900 | #endif
901 | if (opt)
902 | return xstrdup (opt);
903 | return NULL;
904 | }
905 |
906 | int
907 | label_create_file (UNUSED const char *file_label)
908 | {
909 | #ifdef HAVE_SELINUX
910 | if (is_selinux_enabled () > 0 && file_label)
911 | return setfscreatecon (file_label);
912 | #endif
913 | return 0;
914 | }
915 |
916 | int
917 | label_exec (UNUSED const char *exec_label)
918 | {
919 | #ifdef HAVE_SELINUX
920 | if (is_selinux_enabled () > 0 && exec_label)
921 | return setexeccon (exec_label);
922 | #endif
923 | return 0;
924 | }
925 |
926 | /*
927 | * Like strerror(), but specialized for a failed mount(2) call.
928 | */
929 | const char *
930 | mount_strerror (int errsv)
931 | {
932 | switch (errsv)
933 | {
934 | case ENOSPC:
935 | /* "No space left on device" misleads users into thinking there
936 | * is some sort of disk-space problem, but mount(2) uses that
937 | * errno value to mean something more like "limit exceeded". */
938 | return ("Limit exceeded (ENOSPC). "
939 | "(Hint: Check that /proc/sys/fs/mount-max is sufficient, "
940 | "typically 100000)");
941 |
942 | default:
943 | return strerror (errsv);
944 | }
945 | }
946 |
--------------------------------------------------------------------------------
/tests/test-run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -xeuo pipefail
4 |
5 | srcd=$(cd $(dirname "$0") && pwd)
6 |
7 | . ${srcd}/libtest.sh
8 |
9 | bn=$(basename "$0")
10 |
11 | test_count=0
12 | ok () {
13 | test_count=$((test_count + 1))
14 | echo ok $test_count "$@"
15 | }
16 | ok_skip () {
17 | ok "# SKIP" "$@"
18 | }
19 | done_testing () {
20 | echo "1..$test_count"
21 | }
22 |
23 | # Test help
24 | ${BWRAP} --help > help.txt
25 | assert_file_has_content help.txt "usage: ${BWRAP}"
26 | ok "Help works"
27 |
28 | for ALT in "" "--unshare-user-try" "--unshare-pid" "--unshare-user-try --unshare-pid"; do
29 | # Test fuse fs as bind source
30 | if [ "x$FUSE_DIR" != "x" ]; then
31 | $RUN $ALT --proc /proc --dev /dev --bind $FUSE_DIR /tmp/foo true
32 | ok "can bind-mount a FUSE directory with $ALT"
33 | else
34 | ok_skip "no FUSE support"
35 | fi
36 | # no --dev => no devpts => no map_root workaround
37 | $RUN $ALT --proc /proc true
38 | ok "can mount /proc with $ALT"
39 | # No network
40 | $RUN $ALT --unshare-net --proc /proc --dev /dev true
41 | ok "can unshare network, create new /dev with $ALT"
42 | # Unreadable file
43 | echo -n "expect EPERM: " >&2
44 |
45 | # Test caps when bwrap is not setuid
46 | if test -n "${bwrap_is_suid:-}"; then
47 | CAP="--cap-add ALL"
48 | else
49 | CAP=""
50 | fi
51 |
52 | if ! cat /etc/shadow >/dev/null &&
53 | $RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /tmp/foo; then
54 | assert_not_reached Could read /etc/shadow via /tmp/foo bind-mount
55 | fi
56 |
57 | if ! cat /etc/shadow >/dev/null &&
58 | $RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /etc/shadow; then
59 | assert_not_reached Could read /etc/shadow
60 | fi
61 |
62 | ok "cannot read /etc/shadow with $ALT"
63 | # Unreadable dir
64 | if [ "x$UNREADABLE" != "x" ]; then
65 | echo -n "expect EPERM: " >&2
66 | if $RUN $ALT --unshare-net --proc /proc --dev /dev --bind $UNREADABLE /tmp/foo cat /tmp/foo; then
67 | assert_not_reached Could read $UNREADABLE
68 | fi
69 | ok "cannot read $UNREADABLE with $ALT"
70 | else
71 | ok_skip "not sure what unreadable file to use"
72 | fi
73 |
74 | # bind dest in symlink (https://github.com/projectatomic/bubblewrap/pull/119)
75 | $RUN $ALT --dir /tmp/dir --symlink dir /tmp/link --bind /etc /tmp/link true
76 | ok "can bind a destination over a symlink"
77 | done
78 |
79 | # Test symlink behaviour
80 | rm -f ./symlink
81 | $RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/null "$(pwd)/symlink" true >&2
82 | readlink ./symlink > target.txt
83 | assert_file_has_content target.txt /dev/null
84 | ok "--symlink works"
85 | $RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/null "$(pwd)/symlink" true >&2
86 | ok "--symlink is idempotent"
87 | if $RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/full "$(pwd)/symlink" true 2>err.txt; then
88 | fatal "creating a conflicting symlink should have failed"
89 | else
90 | assert_file_has_content err.txt "Can't make symlink .*: existing destination is /dev/null"
91 | fi
92 | ok "--symlink doesn't overwrite a conflicting symlink"
93 |
94 | # Test devices
95 | $RUN --unshare-pid --dev /dev ls -al /dev/{stdin,stdout,stderr,null,random,urandom,fd,core} >/dev/null
96 | ok "all expected devices were created"
97 |
98 | # Test --as-pid-1
99 | $RUN --unshare-pid --as-pid-1 --bind / / bash -c 'echo $$' > as_pid_1.txt
100 | assert_file_has_content as_pid_1.txt "1"
101 | ok "can run as pid 1"
102 |
103 | # Test --info-fd and --json-status-fd
104 | if $RUN --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'exit 42' 42>info.json 43>json-status.json 2>err.txt; then
105 | fatal "should have been exit 42"
106 | fi
107 | assert_file_has_content info.json '"child-pid": [0-9]'
108 | assert_file_has_content json-status.json '"child-pid": [0-9]'
109 | assert_file_has_content_literal json-status.json '"exit-code": 42'
110 | ok "info and json-status fd"
111 |
112 | 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)
113 |
114 | for NS in "ipc" "mnt" "net" "pid" "uts"; do
115 |
116 | want=$(echo "$DATA" | grep "/proc/self/ns/$NS" | awk '{print $2}')
117 | assert_file_has_content info.json "$want"
118 | assert_file_has_content json-status.json "$want"
119 | done
120 |
121 | ok "namespace id info in info and json-status fd"
122 |
123 | if ! command -v strace >/dev/null || ! strace -h | grep -v -e default | grep -e fault >/dev/null; then
124 | ok_skip "no strace fault injection"
125 | else
126 | ! strace -o /dev/null -f -e trace=prctl -e fault=prctl:when=39 $RUN --die-with-parent --json-status-fd 42 true 42>json-status.json
127 | assert_not_file_has_content json-status.json '"exit-code": [0-9]'
128 | ok "pre-exec failure doesn't include exit-code in json-status"
129 | fi
130 |
131 | notanexecutable=/
132 | $RUN --json-status-fd 42 $notanexecutable 42>json-status.json || true
133 | assert_not_file_has_content json-status.json '"exit-code": [0-9]'
134 | ok "exec failure doesn't include exit-code in json-status"
135 |
136 | # These tests require --unshare-user
137 | if test -n "${bwrap_is_suid:-}"; then
138 | ok_skip "no --cap-add support"
139 | ok_skip "no --cap-add support"
140 | ok_skip "no --disable-userns"
141 | else
142 | BWRAP_RECURSE="$BWRAP --unshare-user --uid 0 --gid 0 --cap-add ALL --bind / / --bind /proc /proc"
143 |
144 | # $BWRAP May be inaccessible due to the user namespace so use /proc/self/exe
145 | $BWRAP_RECURSE -- /proc/self/exe --unshare-all --bind / / --bind /proc /proc echo hello > recursive_proc.txt
146 | assert_file_has_content recursive_proc.txt "hello"
147 | ok "can mount /proc recursively"
148 |
149 | $BWRAP_RECURSE -- /proc/self/exe --unshare-all ${BWRAP_RO_HOST_ARGS} findmnt > recursive-newroot.txt
150 | assert_file_has_content recursive-newroot.txt "/usr"
151 | ok "can pivot to new rootfs recursively"
152 |
153 | $BWRAP --dev-bind / / -- true
154 | ! $BWRAP --assert-userns-disabled --dev-bind / / -- true
155 | $BWRAP --unshare-user --disable-userns --dev-bind / / -- true
156 | ! $BWRAP --unshare-user --disable-userns --dev-bind / / -- $BWRAP --dev-bind / / -- true
157 | $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 2 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true"
158 | $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 100 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true"
159 | $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "! $BWRAP --unshare-user --dev-bind / / --assert-userns-disabled -- true"
160 |
161 | $BWRAP_RECURSE --dev-bind / / -- true
162 | ! $BWRAP_RECURSE --assert-userns-disabled --dev-bind / / -- true
163 | $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- true
164 | ! $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- /proc/self/exe --dev-bind / / -- true
165 | $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 2 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true"
166 | $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 100 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true"
167 | $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "! $BWRAP --unshare-user --dev-bind / / --assert-userns-disabled -- true"
168 |
169 | ok "can disable nested userns"
170 | fi
171 |
172 | # Test error prefixing
173 | if $RUN --unshare-pid --bind /source-enoent /dest true 2>err.txt; then
174 | assert_not_reached "bound nonexistent source"
175 | fi
176 | assert_file_has_content err.txt "^bwrap: Can't find source path.*source-enoent"
177 | ok "error prefixing"
178 |
179 | if ! ${is_uidzero}; then
180 | # When invoked as non-root, check that by default we have no caps left
181 | for OPT in "" "--unshare-user-try --as-pid-1" "--unshare-user-try" "--as-pid-1"; do
182 | e=0
183 | $RUN $OPT --unshare-pid getpcaps 1 >&2 2> caps.test || e=$?
184 | sed -e 's/^/# /' < caps.test >&2
185 | test "$e" = 0
186 | assert_not_file_has_content caps.test ': =.*cap'
187 | done
188 | ok "we have no caps as uid != 0"
189 | else
190 | capsh --print | sed -e 's/no-new-privs=0/no-new-privs=1/' > caps.expected
191 |
192 | for OPT in "" "--as-pid-1"; do
193 | $RUN $OPT --unshare-pid capsh --print >caps.test
194 | diff -u caps.expected caps.test
195 | done
196 | # And test that we can drop all, as well as specific caps
197 | $RUN $OPT --cap-drop ALL --unshare-pid capsh --print >caps.test
198 | assert_file_has_content caps.test 'Current: =$'
199 | # Check for dropping kill/fowner (we assume all uid 0 callers have this)
200 | # But we should still have net_bind_service for example
201 | $RUN $OPT --cap-drop CAP_KILL --cap-drop CAP_FOWNER --unshare-pid capsh --print >caps.test
202 | # capsh's output format changed from v2.29 -> drops are now indicated with -eip
203 | if grep 'Current: =.*+eip$' caps.test; then
204 | assert_not_file_has_content caps.test '^Current: =.*cap_kill.*+eip$'
205 | assert_not_file_has_content caps.test '^Current: =.*cap_fowner.*+eip$'
206 | assert_file_has_content caps.test '^Current: =.*cap_net_bind_service.*+eip$'
207 | else
208 | assert_file_has_content caps.test '^Current: =eip.*cap_kill.*-eip$'
209 | assert_file_has_content caps.test '^Current: =eip.*cap_fowner.*-eip$'
210 | assert_not_file_has_content caps.test '^Current: =.*cap_net_bind_service.*-eip$'
211 | fi
212 | ok "we have the expected caps as uid 0"
213 | fi
214 |
215 | # Test --die-with-parent
216 |
217 | cat >lockf-n.py < test.args
263 | printf '%s--dir\0/tmp/hello/world2\0' '' > test.args2
264 | printf '%s--dir\0/tmp/hello/world3\0' '' > test.args3
265 | $RUN --args 3 --args 4 --args 5 /bin/sh -c 'test -d /tmp/hello/world && test -d /tmp/hello/world2 && test -d /tmp/hello/world3' 3 bin/--inadvisable-executable-name--
270 | echo "echo hello" >> bin/--inadvisable-executable-name--
271 | chmod +x bin/--inadvisable-executable-name--
272 | PATH="${srcd}:$PATH" $RUN -- sh -c "echo hello" > stdout
273 | assert_file_has_content stdout hello
274 | ok "we can run with --"
275 | PATH="$(pwd)/bin:$PATH" $RUN -- --inadvisable-executable-name-- > stdout
276 | assert_file_has_content stdout hello
277 | ok "we can run an inadvisable executable name with --"
278 | if $RUN -- --dev-bind /dev /dev sh -c 'echo should not have run'; then
279 | assert_not_reached "'--dev-bind' should have been interpreted as a (silly) executable name"
280 | fi
281 | ok "options like --dev-bind are defanged by --"
282 |
283 | if command -v mktemp > /dev/null; then
284 | tempfile="$(mktemp /tmp/bwrap-test-XXXXXXXX)"
285 | echo "hello" > "$tempfile"
286 | $BWRAP --bind / / cat "$tempfile" > stdout
287 | assert_file_has_content stdout hello
288 | ok "bind-mount of / exposes real /tmp"
289 | $BWRAP --bind / / --bind /tmp /tmp cat "$tempfile" > stdout
290 | assert_file_has_content stdout hello
291 | ok "bind-mount of /tmp exposes real /tmp"
292 | if [ -d /mnt ] && [ ! -L /mnt ]; then
293 | $BWRAP --bind / / --bind /tmp /mnt cat "/mnt/${tempfile#/tmp/}" > stdout
294 | assert_file_has_content stdout hello
295 | ok "bind-mount of /tmp onto /mnt exposes real /tmp"
296 | else
297 | ok_skip "/mnt does not exist or is a symlink"
298 | fi
299 | else
300 | ok_skip "mktemp not found"
301 | ok_skip "mktemp not found"
302 | ok_skip "mktemp not found"
303 | fi
304 |
305 | if $RUN test -d /tmp/oldroot; then
306 | assert_not_reached "/tmp/oldroot should not be visible"
307 | fi
308 | if $RUN test -d /tmp/newroot; then
309 | assert_not_reached "/tmp/newroot should not be visible"
310 | fi
311 |
312 | echo "hello" > input.$$
313 | $BWRAP --bind / / --bind "$(pwd)" /tmp cat /tmp/input.$$ > stdout
314 | assert_file_has_content stdout hello
315 | if $BWRAP --bind / / --bind "$(pwd)" /tmp test -d /tmp/oldroot; then
316 | assert_not_reached "/tmp/oldroot should not be visible"
317 | fi
318 | if $BWRAP --bind / / --bind "$(pwd)" /tmp test -d /tmp/newroot; then
319 | assert_not_reached "/tmp/newroot should not be visible"
320 | fi
321 | ok "we can mount another directory onto /tmp"
322 |
323 | echo "hello" > input.$$
324 | $RUN --bind "$(pwd)" /tmp/here cat /tmp/here/input.$$ > stdout
325 | assert_file_has_content stdout hello
326 | if $RUN --bind "$(pwd)" /tmp/here test -d /tmp/oldroot; then
327 | assert_not_reached "/tmp/oldroot should not be visible"
328 | fi
329 | if $RUN --bind "$(pwd)" /tmp/here test -d /tmp/newroot; then
330 | assert_not_reached "/tmp/newroot should not be visible"
331 | fi
332 | ok "we can mount another directory inside /tmp"
333 |
334 | touch some-file
335 | mkdir -p some-dir
336 | rm -fr new-dir-mountpoint
337 | rm -fr new-file-mountpoint
338 | $RUN \
339 | --bind "$(pwd -P)/some-dir" "$(pwd -P)/new-dir-mountpoint" \
340 | --bind "$(pwd -P)/some-file" "$(pwd -P)/new-file-mountpoint" \
341 | true
342 | command stat -c '%a' new-dir-mountpoint > new-dir-permissions
343 | assert_file_has_content new-dir-permissions 755
344 | command stat -c '%a' new-file-mountpoint > new-file-permissions
345 | assert_file_has_content new-file-permissions 444
346 | ok "Files and directories created as mount points have expected permissions"
347 |
348 |
349 | if [ -S /dev/log ]; then
350 | $RUN --bind / / --bind "$(realpath /dev/log)" "$(realpath /dev/log)" true
351 | ok "Can bind-mount a socket (/dev/log) onto a socket"
352 | else
353 | ok_skip "- /dev/log is not a socket, cannot test bubblewrap#409"
354 | fi
355 |
356 | mkdir -p dir-already-existed
357 | chmod 0710 dir-already-existed
358 | mkdir -p dir-already-existed2
359 | chmod 0754 dir-already-existed2
360 | rm -fr new-dir-default-perms
361 | rm -fr new-dir-set-perms
362 | $RUN \
363 | --perms 1741 --dir "$(pwd -P)/new-dir-set-perms" \
364 | --dir "$(pwd -P)/dir-already-existed" \
365 | --perms 0741 --dir "$(pwd -P)/dir-already-existed2" \
366 | --dir "$(pwd -P)/dir-chmod" \
367 | --chmod 1755 "$(pwd -P)/dir-chmod" \
368 | --dir "$(pwd -P)/new-dir-default-perms" \
369 | true
370 | command stat -c '%a' new-dir-default-perms > new-dir-permissions
371 | assert_file_has_content new-dir-permissions '^755$'
372 | command stat -c '%a' new-dir-set-perms > new-dir-permissions
373 | assert_file_has_content new-dir-permissions '^1741$'
374 | command stat -c '%a' dir-already-existed > dir-permissions
375 | assert_file_has_content dir-permissions '^710$'
376 | command stat -c '%a' dir-already-existed2 > dir-permissions
377 | assert_file_has_content dir-permissions '^754$'
378 | command stat -c '%a' dir-chmod > dir-permissions
379 | assert_file_has_content dir-permissions '^1755$'
380 | ok "Directories created explicitly have expected permissions"
381 |
382 | rm -fr parent
383 | rm -fr parent-of-1777
384 | rm -fr parent-of-0755
385 | rm -fr parent-of-0644
386 | rm -fr parent-of-0750
387 | rm -fr parent-of-0710
388 | rm -fr parent-of-0720
389 | rm -fr parent-of-0640
390 | rm -fr parent-of-0700
391 | rm -fr parent-of-0600
392 | rm -fr parent-of-0705
393 | rm -fr parent-of-0604
394 | rm -fr parent-of-0000
395 | $RUN \
396 | --dir "$(pwd -P)"/parent/dir \
397 | --perms 1777 --dir "$(pwd -P)"/parent-of-1777/dir \
398 | --perms 0755 --dir "$(pwd -P)"/parent-of-0755/dir \
399 | --perms 0644 --dir "$(pwd -P)"/parent-of-0644/dir \
400 | --perms 0750 --dir "$(pwd -P)"/parent-of-0750/dir \
401 | --perms 0710 --dir "$(pwd -P)"/parent-of-0710/dir \
402 | --perms 0720 --dir "$(pwd -P)"/parent-of-0720/dir \
403 | --perms 0640 --dir "$(pwd -P)"/parent-of-0640/dir \
404 | --perms 0700 --dir "$(pwd -P)"/parent-of-0700/dir \
405 | --perms 0600 --dir "$(pwd -P)"/parent-of-0600/dir \
406 | --perms 0705 --dir "$(pwd -P)"/parent-of-0705/dir \
407 | --perms 0604 --dir "$(pwd -P)"/parent-of-0604/dir \
408 | --perms 0000 --dir "$(pwd -P)"/parent-of-0000/dir \
409 | true
410 | command stat -c '%a' parent > dir-permissions
411 | assert_file_has_content dir-permissions '^755$'
412 | command stat -c '%a' parent-of-1777 > dir-permissions
413 | assert_file_has_content dir-permissions '^755$'
414 | command stat -c '%a' parent-of-0755 > dir-permissions
415 | assert_file_has_content dir-permissions '^755$'
416 | command stat -c '%a' parent-of-0644 > dir-permissions
417 | assert_file_has_content dir-permissions '^755$'
418 | command stat -c '%a' parent-of-0750 > dir-permissions
419 | assert_file_has_content dir-permissions '^750$'
420 | command stat -c '%a' parent-of-0710 > dir-permissions
421 | assert_file_has_content dir-permissions '^750$'
422 | command stat -c '%a' parent-of-0720 > dir-permissions
423 | assert_file_has_content dir-permissions '^750$'
424 | command stat -c '%a' parent-of-0640 > dir-permissions
425 | assert_file_has_content dir-permissions '^750$'
426 | command stat -c '%a' parent-of-0700 > dir-permissions
427 | assert_file_has_content dir-permissions '^700$'
428 | command stat -c '%a' parent-of-0600 > dir-permissions
429 | assert_file_has_content dir-permissions '^700$'
430 | command stat -c '%a' parent-of-0705 > dir-permissions
431 | assert_file_has_content dir-permissions '^705$'
432 | command stat -c '%a' parent-of-0604 > dir-permissions
433 | assert_file_has_content dir-permissions '^705$'
434 | command stat -c '%a' parent-of-0000 > dir-permissions
435 | assert_file_has_content dir-permissions '^700$'
436 | chmod -R 0700 parent*
437 | rm -fr parent*
438 | ok "Directories created as parents have expected permissions"
439 |
440 | $RUN \
441 | --perms 01777 --tmpfs "$(pwd -P)" \
442 | cat /proc/self/mountinfo >&2
443 | $RUN \
444 | --perms 01777 --tmpfs "$(pwd -P)" \
445 | stat -c '%a' "$(pwd -P)" > dir-permissions
446 | assert_file_has_content dir-permissions '^1777$'
447 | $RUN \
448 | --tmpfs "$(pwd -P)" \
449 | stat -c '%a' "$(pwd -P)" > dir-permissions
450 | assert_file_has_content dir-permissions '^755$'
451 | ok "tmpfs has expected permissions"
452 |
453 | # 1048576 = 1 MiB
454 | if test -n "${bwrap_is_suid:-}"; then
455 | if $RUN --size 1048576 --tmpfs "$(pwd -P)" true; then
456 | assert_not_reached "Should not allow --size --tmpfs when setuid"
457 | fi
458 | ok "--size --tmpfs is not allowed when setuid"
459 | elif df --output=size --block-size=1K "$(pwd -P)" >/dev/null 2>/dev/null; then
460 | $RUN \
461 | --size 1048576 --tmpfs "$(pwd -P)" \
462 | df --output=size --block-size=1K "$(pwd -P)" > dir-size
463 | assert_file_has_content dir-size '^ *1024$'
464 | $RUN \
465 | --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" \
466 | stat -c '%a' "$(pwd -P)" > dir-permissions
467 | assert_file_has_content dir-permissions '^1777$'
468 | $RUN \
469 | --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" \
470 | df --output=size --block-size=1K "$(pwd -P)" > dir-size
471 | assert_file_has_content dir-size '^ *1024$'
472 | $RUN \
473 | --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" \
474 | stat -c '%a' "$(pwd -P)" > dir-permissions
475 | assert_file_has_content dir-permissions '^1777$'
476 | $RUN \
477 | --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" \
478 | df --output=size --block-size=1K "$(pwd -P)" > dir-size
479 | assert_file_has_content dir-size '^ *1024$'
480 | ok "tmpfs has expected size"
481 | else
482 | $RUN --size 1048576 --tmpfs "$(pwd -P)" true
483 | $RUN --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" true
484 | $RUN --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" true
485 | ok_skip "df is too old, cannot test --size --tmpfs fully"
486 | fi
487 |
488 | $RUN \
489 | --file 0 /tmp/file \
490 | stat -c '%a' /tmp/file < /dev/null > file-permissions
491 | assert_file_has_content file-permissions '^666$'
492 | $RUN \
493 | --perms 0640 --file 0 /tmp/file \
494 | stat -c '%a' /tmp/file < /dev/null > file-permissions
495 | assert_file_has_content file-permissions '^640$'
496 | $RUN \
497 | --bind-data 0 /tmp/file \
498 | stat -c '%a' /tmp/file < /dev/null > file-permissions
499 | assert_file_has_content file-permissions '^600$'
500 | $RUN \
501 | --perms 0640 --bind-data 0 /tmp/file \
502 | stat -c '%a' /tmp/file < /dev/null > file-permissions
503 | assert_file_has_content file-permissions '^640$'
504 | $RUN \
505 | --ro-bind-data 0 /tmp/file \
506 | stat -c '%a' /tmp/file < /dev/null > file-permissions
507 | assert_file_has_content file-permissions '^600$'
508 | $RUN \
509 | --perms 0640 --ro-bind-data 0 /tmp/file \
510 | stat -c '%a' /tmp/file < /dev/null > file-permissions
511 | assert_file_has_content file-permissions '^640$'
512 | ok "files have expected permissions"
513 |
514 | if $RUN --size 0 --tmpfs /tmp/a true; then
515 | assert_not_reached Zero tmpfs size allowed
516 | fi
517 | if $RUN --size 123bogus --tmpfs /tmp/a true; then
518 | assert_not_reached Bogus tmpfs size allowed
519 | fi
520 | if $RUN --size '' --tmpfs /tmp/a true; then
521 | assert_not_reached Empty tmpfs size allowed
522 | fi
523 | if $RUN --size -12345678 --tmpfs /tmp/a true; then
524 | assert_not_reached Negative tmpfs size allowed
525 | fi
526 | if $RUN --size ' -12345678' --tmpfs /tmp/a true; then
527 | assert_not_reached Negative tmpfs size with space allowed
528 | fi
529 | # This is 2^64
530 | if $RUN --size 18446744073709551616 --tmpfs /tmp/a true; then
531 | assert_not_reached Overflowing tmpfs size allowed
532 | fi
533 | # This is 2^63 + 1; note that the current max size is SIZE_MAX/2
534 | if $RUN --size 9223372036854775809 --tmpfs /tmp/a true; then
535 | assert_not_reached Too-large tmpfs size allowed
536 | fi
537 | ok "bogus tmpfs size not allowed"
538 |
539 | if $RUN --perms 0640 --perms 0640 --tmpfs /tmp/a true; then
540 | assert_not_reached Multiple perms options allowed
541 | fi
542 | if $RUN --size 1048576 --size 1048576 --tmpfs /tmp/a true; then
543 | assert_not_reached Multiple perms options allowed
544 | fi
545 | ok "--perms and --size only allowed once"
546 |
547 |
548 | FOO= BAR=baz $RUN --setenv FOO bar sh -c 'echo "$FOO$BAR"' > stdout
549 | assert_file_has_content stdout barbaz
550 | FOO=wrong BAR=baz $RUN --setenv FOO bar sh -c 'echo "$FOO$BAR"' > stdout
551 | assert_file_has_content stdout barbaz
552 | FOO=wrong BAR=baz $RUN --unsetenv FOO sh -c 'printf "%s%s" "$FOO" "$BAR"' > stdout
553 | printf baz > reference
554 | assert_files_equal stdout reference
555 | FOO=wrong BAR=wrong $RUN --clearenv /usr/bin/env > stdout
556 | echo "PWD=$(pwd -P)" > reference
557 | assert_files_equal stdout reference
558 | ok "environment manipulation"
559 |
560 | $RUN sh -c 'echo $0' > stdout
561 | assert_file_has_content stdout sh
562 | $RUN --argv0 sh sh -c 'echo $0' > stdout
563 | assert_file_has_content stdout sh
564 | $RUN --argv0 right sh -c 'echo $0' > stdout
565 | assert_file_has_content stdout right
566 | ok "argv0 manipulation"
567 |
568 | echo "foobar" > file-data
569 | $RUN --proc /proc --dev /dev --bind / / --bind-fd 100 /tmp cat /tmp/file-data 100< . > stdout
570 | assert_file_has_content stdout foobar
571 |
572 | ok "bind-fd"
573 |
574 | done_testing
575 |
--------------------------------------------------------------------------------