The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .dir-locals.el
├── .editorconfig
├── .github
    └── workflows
    │   └── check.yml
├── CODE-OF-CONDUCT.md
├── COPYING
├── LICENSE
├── NEWS.md
├── README.md
├── SECURITY.md
├── bind-mount.c
├── bind-mount.h
├── bubblewrap.c
├── bubblewrap.jpg
├── bwrap.xml
├── ci
    └── builddeps.sh
├── completions
    ├── bash
    │   ├── bwrap
    │   └── meson.build
    ├── meson.build
    └── zsh
    │   ├── _bwrap
    │   └── meson.build
├── demos
    ├── bubblewrap-shell.sh
    ├── flatpak-run.sh
    ├── flatpak.bpf
    └── userns-block-fd.py
├── meson.build
├── meson_options.txt
├── network.c
├── network.h
├── packaging
    └── bubblewrap.spec
├── release-checklist.md
├── tests
    ├── libtest-core.sh
    ├── libtest.sh
    ├── meson.build
    ├── test-run.sh
    ├── test-seccomp.py
    ├── test-specifying-pidns.sh
    ├── test-specifying-userns.sh
    ├── test-utils.c
    ├── try-syscall.c
    └── use-as-subproject
    │   ├── .gitignore
    │   ├── README
    │   ├── assert-correct-rpath.py
    │   ├── config.h
    │   ├── dummy-config.h.in
    │   └── meson.build
├── uncrustify.cfg
├── uncrustify.sh
├── utils.c
└── utils.h


/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ((c-mode . ((indent-tabs-mode . nil) (c-file-style . "gnu"))))
2 | 


--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.[ch]]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | indent_brace_style = gnu
6 | 


--------------------------------------------------------------------------------
/.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 |   meson:
 13 |     name: Build with Meson and gcc, and test
 14 |     runs-on: ubuntu-latest
 15 |     steps:
 16 |     - name: Check out
 17 |       uses: actions/checkout@v4
 18 |     - name: Install build-dependencies
 19 |       run: sudo ./ci/builddeps.sh
 20 |     - name: Create logs dir
 21 |       run: mkdir test-logs
 22 |     - name: setup
 23 |       run: |
 24 |         meson _build
 25 |       env:
 26 |         CFLAGS: >-
 27 |           -O2
 28 |           -Wp,-D_FORTIFY_SOURCE=2
 29 |           -fsanitize=address
 30 |           -fsanitize=undefined
 31 |     - name: compile
 32 |       run: ninja -C _build -v
 33 |     - name: smoke-test
 34 |       run: |
 35 |         set -x
 36 |         ./_build/bwrap --bind / / --tmpfs /tmp true
 37 |       env:
 38 |         ASAN_OPTIONS: detect_leaks=0
 39 |     - name: test
 40 |       run: |
 41 |         BWRAP_MUST_WORK=1 meson test -C _build
 42 |       env:
 43 |         ASAN_OPTIONS: detect_leaks=0
 44 |     - name: Collect overall test logs on failure
 45 |       if: failure()
 46 |       run: mv _build/meson-logs/testlog.txt test-logs/ || true
 47 |     - name: install
 48 |       run: |
 49 |         DESTDIR="$(pwd)/DESTDIR" meson install -C _build
 50 |         ( cd DESTDIR && find -ls )
 51 |     - name: dist
 52 |       run: |
 53 |         BWRAP_MUST_WORK=1 meson dist -C _build
 54 |     - name: Collect dist test logs on failure
 55 |       if: failure()
 56 |       run: mv _build/meson-private/dist-build/meson-logs/testlog.txt test-logs/disttestlog.txt || true
 57 |     - name: use as subproject
 58 |       run: |
 59 |         mkdir tests/use-as-subproject/subprojects
 60 |         tar -C tests/use-as-subproject/subprojects -xf _build/meson-dist/bubblewrap-*.tar.xz
 61 |         mv tests/use-as-subproject/subprojects/bubblewrap-* tests/use-as-subproject/subprojects/bubblewrap
 62 |         ( cd tests/use-as-subproject && meson _build )
 63 |         ninja -C tests/use-as-subproject/_build -v
 64 |         meson test -C tests/use-as-subproject/_build
 65 |         DESTDIR="$(pwd)/DESTDIR-as-subproject" meson install -C tests/use-as-subproject/_build
 66 |         ( cd DESTDIR-as-subproject && find -ls )
 67 |         test -x DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap
 68 |         test ! -e DESTDIR-as-subproject/usr/local/bin/bwrap
 69 |         test ! -e DESTDIR-as-subproject/usr/local/libexec/bwrap
 70 |         tests/use-as-subproject/assert-correct-rpath.py DESTDIR-as-subproject/usr/local/libexec/not-flatpak-bwrap
 71 |     - name: Upload test logs
 72 |       uses: actions/upload-artifact@v4
 73 |       if: failure() || cancelled()
 74 |       with:
 75 |         name: test logs
 76 |         path: test-logs
 77 | 
 78 |   clang:
 79 |     name: Build with clang and analyze
 80 |     runs-on: ubuntu-latest
 81 |     strategy:
 82 |       fail-fast: false
 83 |       matrix:
 84 |         language:
 85 |           - cpp
 86 |     steps:
 87 |     - name: Initialize CodeQL
 88 |       uses: github/codeql-action/init@v2
 89 |       with:
 90 |         languages: ${{ matrix.language }}
 91 |     - name: Check out
 92 |       uses: actions/checkout@v4
 93 |     - name: Install build-dependencies
 94 |       run: sudo ./ci/builddeps.sh --clang
 95 |     - run: meson build -Dselinux=enabled
 96 |       env:
 97 |         CC: clang
 98 |         CFLAGS: >-
 99 |           -O2
100 |           -Werror=unused-variable
101 |     - run: meson compile -C build
102 |     - name: CodeQL analysis
103 |       uses: github/codeql-action/analyze@v2
104 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
  1 |                   GNU LIBRARY GENERAL PUBLIC LICENSE
  2 |                        Version 2, June 1991
  3 | 
  4 |  Copyright (C) 1991 Free Software Foundation, Inc.
  5 |  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  6 |  Everyone is permitted to copy and distribute verbatim copies
  7 |  of this license document, but changing it is not allowed.
  8 | 
  9 | [This is the first released version of the library GPL.  It is
 10 |  numbered 2 because it goes with version 2 of the ordinary GPL.]
 11 | 
 12 |                             Preamble
 13 | 
 14 |   The licenses for most software are designed to take away your
 15 | freedom to share and change it.  By contrast, the GNU General Public
 16 | Licenses are intended to guarantee your freedom to share and change
 17 | free software--to make sure the software is free for all its users.
 18 | 
 19 |   This license, the Library General Public License, applies to some
 20 | specially designated Free Software Foundation software, and to any
 21 | other libraries whose authors decide to use it.  You can use it for
 22 | your libraries, too.
 23 | 
 24 |   When we speak of free software, we are referring to freedom, not
 25 | price.  Our General Public Licenses are designed to make sure that you
 26 | have the freedom to distribute copies of free software (and charge for
 27 | this service if you wish), that you receive source code or can get it
 28 | if you want it, that you can change the software or use pieces of it
 29 | in new free programs; and that you know you can do these things.
 30 | 
 31 |   To protect your rights, we need to make restrictions that forbid
 32 | anyone to deny you these rights or to ask you to surrender the rights.
 33 | These restrictions translate to certain responsibilities for you if
 34 | you distribute copies of the library, or if you modify it.
 35 | 
 36 |   For example, if you distribute copies of the library, whether gratis
 37 | or for a fee, you must give the recipients all the rights that we gave
 38 | you.  You must make sure that they, too, receive or can get the source
 39 | code.  If you link a program with the library, you must provide
 40 | complete object files to the recipients so that they can relink them
 41 | with the library, after making changes to the library and recompiling
 42 | it.  And you must show them these terms so they know their rights.
 43 | 
 44 |   Our method of protecting your rights has two steps: (1) copyright
 45 | the library, and (2) offer you this license which gives you legal
 46 | permission to copy, distribute and/or modify the library.
 47 | 
 48 |   Also, for each distributor's protection, we want to make certain
 49 | that everyone understands that there is no warranty for this free
 50 | library.  If the library is modified by someone else and passed on, we
 51 | want its recipients to know that what they have is not the original
 52 | version, so that any problems introduced by others will not reflect on
 53 | the original authors' reputations.
 54 | 
 55 |   Finally, any free program is threatened constantly by software
 56 | patents.  We wish to avoid the danger that companies distributing free
 57 | software will individually obtain patent licenses, thus in effect
 58 | transforming the program into proprietary software.  To prevent this,
 59 | we have made it clear that any patent must be licensed for everyone's
 60 | free use or not licensed at all.
 61 | 
 62 |   Most GNU software, including some libraries, is covered by the ordinary
 63 | GNU General Public License, which was designed for utility programs.  This
 64 | license, the GNU Library General Public License, applies to certain
 65 | designated libraries.  This license is quite different from the ordinary
 66 | one; be sure to read it in full, and don't assume that anything in it is
 67 | the same as in the ordinary license.
 68 | 
 69 |   The reason we have a separate public license for some libraries is that
 70 | they blur the distinction we usually make between modifying or adding to a
 71 | program and simply using it.  Linking a program with a library, without
 72 | changing the library, is in some sense simply using the library, and is
 73 | analogous to running a utility program or application program.  However, in
 74 | a textual and legal sense, the linked executable is a combined work, a
 75 | derivative of the original library, and the ordinary General Public License
 76 | treats it as such.
 77 | 
 78 |   Because of this blurred distinction, using the ordinary General
 79 | Public License for libraries did not effectively promote software
 80 | sharing, because most developers did not use the libraries.  We
 81 | concluded that weaker conditions might promote sharing better.
 82 | 
 83 |   However, unrestricted linking of non-free programs would deprive the
 84 | users of those programs of all benefit from the free status of the
 85 | libraries themselves.  This Library General Public License is intended to
 86 | permit developers of non-free programs to use free libraries, while
 87 | preserving your freedom as a user of such programs to change the free
 88 | libraries that are incorporated in them.  (We have not seen how to achieve
 89 | this as regards changes in header files, but we have achieved it as regards
 90 | changes in the actual functions of the Library.)  The hope is that this
 91 | will lead to faster development of free libraries.
 92 | 
 93 |   The precise terms and conditions for copying, distribution and
 94 | modification follow.  Pay close attention to the difference between a
 95 | "work based on the library" and a "work that uses the library".  The
 96 | former contains code derived from the library, while the latter only
 97 | works together with the library.
 98 | 
 99 |   Note that it is possible for a library to be covered by the ordinary
100 | General Public License rather than by this special one.
101 | 
102 |                   GNU LIBRARY GENERAL PUBLIC LICENSE
103 |    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
104 | 
105 |   0. This License Agreement applies to any software library which
106 | contains a notice placed by the copyright holder or other authorized
107 | party saying it may be distributed under the terms of this Library
108 | General Public License (also called "this License").  Each licensee is
109 | addressed as "you".
110 | 
111 |   A "library" means a collection of software functions and/or data
112 | prepared so as to be conveniently linked with application programs
113 | (which use some of those functions and data) to form executables.
114 | 
115 |   The "Library", below, refers to any such software library or work
116 | which has been distributed under these terms.  A "work based on the
117 | Library" means either the Library or any derivative work under
118 | copyright law: that is to say, a work containing the Library or a
119 | portion of it, either verbatim or with modifications and/or translated
120 | straightforwardly into another language.  (Hereinafter, translation is
121 | included without limitation in the term "modification".)
122 | 
123 |   "Source code" for a work means the preferred form of the work for
124 | making modifications to it.  For a library, complete source code means
125 | all the source code for all modules it contains, plus any associated
126 | interface definition files, plus the scripts used to control compilation
127 | and installation of the library.
128 | 
129 |   Activities other than copying, distribution and modification are not
130 | covered by this License; they are outside its scope.  The act of
131 | running a program using the Library is not restricted, and output from
132 | such a program is covered only if its contents constitute a work based
133 | on the Library (independent of the use of the Library in a tool for
134 | writing it).  Whether that is true depends on what the Library does
135 | and what the program that uses the Library does.
136 |   
137 |   1. You may copy and distribute verbatim copies of the Library's
138 | complete source code as you receive it, in any medium, provided that
139 | you conspicuously and appropriately publish on each copy an
140 | appropriate copyright notice and disclaimer of warranty; keep intact
141 | all the notices that refer to this License and to the absence of any
142 | warranty; and distribute a copy of this License along with the
143 | Library.
144 | 
145 |   You may charge a fee for the physical act of transferring a copy,
146 | and you may at your option offer warranty protection in exchange for a
147 | fee.
148 | 
149 |   2. You may modify your copy or copies of the Library or any portion
150 | of it, thus forming a work based on the Library, and copy and
151 | distribute such modifications or work under the terms of Section 1
152 | above, provided that you also meet all of these conditions:
153 | 
154 |     a) The modified work must itself be a software library.
155 | 
156 |     b) You must cause the files modified to carry prominent notices
157 |     stating that you changed the files and the date of any change.
158 | 
159 |     c) You must cause the whole of the work to be licensed at no
160 |     charge to all third parties under the terms of this License.
161 | 
162 |     d) If a facility in the modified Library refers to a function or a
163 |     table of data to be supplied by an application program that uses
164 |     the facility, other than as an argument passed when the facility
165 |     is invoked, then you must make a good faith effort to ensure that,
166 |     in the event an application does not supply such function or
167 |     table, the facility still operates, and performs whatever part of
168 |     its purpose remains meaningful.
169 | 
170 |     (For example, a function in a library to compute square roots has
171 |     a purpose that is entirely well-defined independent of the
172 |     application.  Therefore, Subsection 2d requires that any
173 |     application-supplied function or table used by this function must
174 |     be optional: if the application does not supply it, the square
175 |     root function must still compute square roots.)
176 | 
177 | These requirements apply to the modified work as a whole.  If
178 | identifiable sections of that work are not derived from the Library,
179 | and can be reasonably considered independent and separate works in
180 | themselves, then this License, and its terms, do not apply to those
181 | sections when you distribute them as separate works.  But when you
182 | distribute the same sections as part of a whole which is a work based
183 | on the Library, the distribution of the whole must be on the terms of
184 | this License, whose permissions for other licensees extend to the
185 | entire whole, and thus to each and every part regardless of who wrote
186 | it.
187 | 
188 | Thus, it is not the intent of this section to claim rights or contest
189 | your rights to work written entirely by you; rather, the intent is to
190 | exercise the right to control the distribution of derivative or
191 | collective works based on the Library.
192 | 
193 | In addition, mere aggregation of another work not based on the Library
194 | with the Library (or with a work based on the Library) on a volume of
195 | a storage or distribution medium does not bring the other work under
196 | the scope of this License.
197 | 
198 |   3. You may opt to apply the terms of the ordinary GNU General Public
199 | License instead of this License to a given copy of the Library.  To do
200 | this, you must alter all the notices that refer to this License, so
201 | that they refer to the ordinary GNU General Public License, version 2,
202 | instead of to this License.  (If a newer version than version 2 of the
203 | ordinary GNU General Public License has appeared, then you can specify
204 | that version instead if you wish.)  Do not make any other change in
205 | these notices.
206 | 
207 |   Once this change is made in a given copy, it is irreversible for
208 | that copy, so the ordinary GNU General Public License applies to all
209 | subsequent copies and derivative works made from that copy.
210 | 
211 |   This option is useful when you wish to copy part of the code of
212 | the Library into a program that is not a library.
213 | 
214 |   4. You may copy and distribute the Library (or a portion or
215 | derivative of it, under Section 2) in object code or executable form
216 | under the terms of Sections 1 and 2 above provided that you accompany
217 | it with the complete corresponding machine-readable source code, which
218 | must be distributed under the terms of Sections 1 and 2 above on a
219 | medium customarily used for software interchange.
220 | 
221 |   If distribution of object code is made by offering access to copy
222 | from a designated place, then offering equivalent access to copy the
223 | source code from the same place satisfies the requirement to
224 | distribute the source code, even though third parties are not
225 | compelled to copy the source along with the object code.
226 | 
227 |   5. A program that contains no derivative of any portion of the
228 | Library, but is designed to work with the Library by being compiled or
229 | linked with it, is called a "work that uses the Library".  Such a
230 | work, in isolation, is not a derivative work of the Library, and
231 | therefore falls outside the scope of this License.
232 | 
233 |   However, linking a "work that uses the Library" with the Library
234 | creates an executable that is a derivative of the Library (because it
235 | contains portions of the Library), rather than a "work that uses the
236 | library".  The executable is therefore covered by this License.
237 | Section 6 states terms for distribution of such executables.
238 | 
239 |   When a "work that uses the Library" uses material from a header file
240 | that is part of the Library, the object code for the work may be a
241 | derivative work of the Library even though the source code is not.
242 | Whether this is true is especially significant if the work can be
243 | linked without the Library, or if the work is itself a library.  The
244 | threshold for this to be true is not precisely defined by law.
245 | 
246 |   If such an object file uses only numerical parameters, data
247 | structure layouts and accessors, and small macros and small inline
248 | functions (ten lines or less in length), then the use of the object
249 | file is unrestricted, regardless of whether it is legally a derivative
250 | work.  (Executables containing this object code plus portions of the
251 | Library will still fall under Section 6.)
252 | 
253 |   Otherwise, if the work is a derivative of the Library, you may
254 | distribute the object code for the work under the terms of Section 6.
255 | Any executables containing that work also fall under Section 6,
256 | whether or not they are linked directly with the Library itself.
257 | 
258 |   6. As an exception to the Sections above, you may also compile or
259 | link a "work that uses the Library" with the Library to produce a
260 | work containing portions of the Library, and distribute that work
261 | under terms of your choice, provided that the terms permit
262 | modification of the work for the customer's own use and reverse
263 | engineering for debugging such modifications.
264 | 
265 |   You must give prominent notice with each copy of the work that the
266 | Library is used in it and that the Library and its use are covered by
267 | this License.  You must supply a copy of this License.  If the work
268 | during execution displays copyright notices, you must include the
269 | copyright notice for the Library among them, as well as a reference
270 | directing the user to the copy of this License.  Also, you must do one
271 | of these things:
272 | 
273 |     a) Accompany the work with the complete corresponding
274 |     machine-readable source code for the Library including whatever
275 |     changes were used in the work (which must be distributed under
276 |     Sections 1 and 2 above); and, if the work is an executable linked
277 |     with the Library, with the complete machine-readable "work that
278 |     uses the Library", as object code and/or source code, so that the
279 |     user can modify the Library and then relink to produce a modified
280 |     executable containing the modified Library.  (It is understood
281 |     that the user who changes the contents of definitions files in the
282 |     Library will not necessarily be able to recompile the application
283 |     to use the modified definitions.)
284 | 
285 |     b) Accompany the work with a written offer, valid for at
286 |     least three years, to give the same user the materials
287 |     specified in Subsection 6a, above, for a charge no more
288 |     than the cost of performing this distribution.
289 | 
290 |     c) If distribution of the work is made by offering access to copy
291 |     from a designated place, offer equivalent access to copy the above
292 |     specified materials from the same place.
293 | 
294 |     d) Verify that the user has already received a copy of these
295 |     materials or that you have already sent this user a copy.
296 | 
297 |   For an executable, the required form of the "work that uses the
298 | Library" must include any data and utility programs needed for
299 | reproducing the executable from it.  However, as a special exception,
300 | the source code distributed need not include anything that is normally
301 | distributed (in either source or binary form) with the major
302 | components (compiler, kernel, and so on) of the operating system on
303 | which the executable runs, unless that component itself accompanies
304 | the executable.
305 | 
306 |   It may happen that this requirement contradicts the license
307 | restrictions of other proprietary libraries that do not normally
308 | accompany the operating system.  Such a contradiction means you cannot
309 | use both them and the Library together in an executable that you
310 | distribute.
311 | 
312 |   7. You may place library facilities that are a work based on the
313 | Library side-by-side in a single library together with other library
314 | facilities not covered by this License, and distribute such a combined
315 | library, provided that the separate distribution of the work based on
316 | the Library and of the other library facilities is otherwise
317 | permitted, and provided that you do these two things:
318 | 
319 |     a) Accompany the combined library with a copy of the same work
320 |     based on the Library, uncombined with any other library
321 |     facilities.  This must be distributed under the terms of the
322 |     Sections above.
323 | 
324 |     b) Give prominent notice with the combined library of the fact
325 |     that part of it is a work based on the Library, and explaining
326 |     where to find the accompanying uncombined form of the same work.
327 | 
328 |   8. You may not copy, modify, sublicense, link with, or distribute
329 | the Library except as expressly provided under this License.  Any
330 | attempt otherwise to copy, modify, sublicense, link with, or
331 | distribute the Library is void, and will automatically terminate your
332 | rights under this License.  However, parties who have received copies,
333 | or rights, from you under this License will not have their licenses
334 | terminated so long as such parties remain in full compliance.
335 | 
336 |   9. You are not required to accept this License, since you have not
337 | signed it.  However, nothing else grants you permission to modify or
338 | distribute the Library or its derivative works.  These actions are
339 | prohibited by law if you do not accept this License.  Therefore, by
340 | modifying or distributing the Library (or any work based on the
341 | Library), you indicate your acceptance of this License to do so, and
342 | all its terms and conditions for copying, distributing or modifying
343 | the Library or works based on it.
344 | 
345 |   10. Each time you redistribute the Library (or any work based on the
346 | Library), the recipient automatically receives a license from the
347 | original licensor to copy, distribute, link with or modify the Library
348 | subject to these terms and conditions.  You may not impose any further
349 | restrictions on the recipients' exercise of the rights granted herein.
350 | You are not responsible for enforcing compliance by third parties to
351 | this License.
352 | 
353 |   11. If, as a consequence of a court judgment or allegation of patent
354 | infringement or for any other reason (not limited to patent issues),
355 | conditions are imposed on you (whether by court order, agreement or
356 | otherwise) that contradict the conditions of this License, they do not
357 | excuse you from the conditions of this License.  If you cannot
358 | distribute so as to satisfy simultaneously your obligations under this
359 | License and any other pertinent obligations, then as a consequence you
360 | may not distribute the Library at all.  For example, if a patent
361 | license would not permit royalty-free redistribution of the Library by
362 | all those who receive copies directly or indirectly through you, then
363 | the only way you could satisfy both it and this License would be to
364 | refrain entirely from distribution of the Library.
365 | 
366 | If any portion of this section is held invalid or unenforceable under any
367 | particular circumstance, the balance of the section is intended to apply,
368 | and the section as a whole is intended to apply in other circumstances.
369 | 
370 | It is not the purpose of this section to induce you to infringe any
371 | patents or other property right claims or to contest validity of any
372 | such claims; this section has the sole purpose of protecting the
373 | integrity of the free software distribution system which is
374 | implemented by public license practices.  Many people have made
375 | generous contributions to the wide range of software distributed
376 | through that system in reliance on consistent application of that
377 | system; it is up to the author/donor to decide if he or she is willing
378 | to distribute software through any other system and a licensee cannot
379 | impose that choice.
380 | 
381 | This section is intended to make thoroughly clear what is believed to
382 | be a consequence of the rest of this License.
383 | 
384 |   12. If the distribution and/or use of the Library is restricted in
385 | certain countries either by patents or by copyrighted interfaces, the
386 | original copyright holder who places the Library under this License may add
387 | an explicit geographical distribution limitation excluding those countries,
388 | so that distribution is permitted only in or among countries not thus
389 | excluded.  In such case, this License incorporates the limitation as if
390 | written in the body of this License.
391 | 
392 |   13. The Free Software Foundation may publish revised and/or new
393 | versions of the Library General Public License from time to time.
394 | Such new versions will be similar in spirit to the present version,
395 | but may differ in detail to address new problems or concerns.
396 | 
397 | Each version is given a distinguishing version number.  If the Library
398 | specifies a version number of this License which applies to it and
399 | "any later version", you have the option of following the terms and
400 | conditions either of that version or of any later version published by
401 | the Free Software Foundation.  If the Library does not specify a
402 | license version number, you may choose any version ever published by
403 | the Free Software Foundation.
404 | 
405 |   14. If you wish to incorporate parts of the Library into other free
406 | programs whose distribution conditions are incompatible with these,
407 | write to the author to ask for permission.  For software which is
408 | copyrighted by the Free Software Foundation, write to the Free
409 | Software Foundation; we sometimes make exceptions for this.  Our
410 | decision will be guided by the two goals of preserving the free status
411 | of all derivatives of our free software and of promoting the sharing
412 | and reuse of software generally.
413 | 
414 |                             NO WARRANTY
415 | 
416 |   15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
417 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
418 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
419 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
420 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
421 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
422 | PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
423 | LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
424 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
425 | 
426 |   16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
427 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
428 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
429 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
430 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
431 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
432 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
433 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
434 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
435 | DAMAGES.
436 | 
437 |                      END OF TERMS AND CONDITIONS
438 | 
439 |            How to Apply These Terms to Your New Libraries
440 | 
441 |   If you develop a new library, and you want it to be of the greatest
442 | possible use to the public, we recommend making it free software that
443 | everyone can redistribute and change.  You can do so by permitting
444 | redistribution under these terms (or, alternatively, under the terms of the
445 | ordinary General Public License).
446 | 
447 |   To apply these terms, attach the following notices to the library.  It is
448 | safest to attach them to the start of each source file to most effectively
449 | convey the exclusion of warranty; and each file should have at least the
450 | "copyright" line and a pointer to where the full notice is found.
451 | 
452 |     <one line to give the library's name and a brief idea of what it does.>
453 |     Copyright (C) <year>  <name of author>
454 | 
455 |     This library is free software; you can redistribute it and/or
456 |     modify it under the terms of the GNU Library General Public
457 |     License as published by the Free Software Foundation; either
458 |     version 2 of the License, or (at your option) any later version.
459 | 
460 |     This library is distributed in the hope that it will be useful,
461 |     but WITHOUT ANY WARRANTY; without even the implied warranty of
462 |     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
463 |     Library General Public License for more details.
464 | 
465 |     You should have received a copy of the GNU Library General Public
466 |     License along with this library; if not, write to the Free Software
467 |     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
468 | 
469 | Also add information on how to contact you by electronic and paper mail.
470 | 
471 | You should also get your employer (if you work as a programmer) or your
472 | school, if any, to sign a "copyright disclaimer" for the library, if
473 | necessary.  Here is a sample; alter the names:
474 | 
475 |   Yoyodyne, Inc., hereby disclaims all copyright interest in the
476 |   library `Frob' (a library for tweaking knobs) written by James Random Hacker.
477 | 
478 |   <signature of Ty Coon>, 1 April 1990
479 |   Ty Coon, President of Vice
480 | 
481 | That's all there is to it!
482 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | COPYING


--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
 1 | bubblewrap 0.11.0
 2 | =================
 3 | 
 4 | Released: 2024-10-30
 5 | 
 6 | Dependencies:
 7 | 
 8 |   * Remove the Autotools build system. Meson ≥ 0.49.0 is now required
 9 |     at build-time. (#625, Hugo Osvaldo Barrera)
10 | 
11 |   * For users of bash-completion, bash-completion ≥ 2.10 is recommended.
12 |     With older bash-completion, bubblewrap might install completions
13 |     outside its `${prefix}` unless overridden with `-Dbash_completion_dir=…`.
14 | 
15 | Enhancements:
16 | 
17 |   * New `--overlay`, `--tmp-overlay`, `--ro-overlay` and `--overlay-src`
18 |     options allow creation of overlay mounts.
19 |     This feature is not available when bubblewrap is installed setuid.
20 |     (#412, #663; Ryan Hendrickson, William Manley, Simon McVittie)
21 | 
22 |   * New `--level-prefix` option produces output that can be parsed by
23 |     tools like `logger --prio-prefix` and `systemd-cat --level-prefix=1`
24 |     (#646, Simon McVittie)
25 | 
26 | Bug fixes:
27 | 
28 |   * Handle `EINTR` when doing I/O on files or sockets (#657, Simon McVittie)
29 | 
30 |   * Don't make assumptions about alignment of socket control message data
31 |     (#637, Simon McVittie)
32 | 
33 |   * Silence some Meson deprecation warnings (#647, @Sertonix)
34 | 
35 |   * Update URLs in documentation to https (#566, @TotalCaesar659)
36 | 
37 |   * Improve tests' compatibility with busybox (#627, @Sertonix)
38 | 
39 |   * Improve compatibility with Meson < 1.3.0 (#664, Simon McVittie)
40 | 
41 | Internal changes:
42 | 
43 |   * Consistently use `<stdbool.h>` for booleans (#660, Simon McVittie)
44 | 
45 |   * Avoid `-Wshadow` compiler warnings (#661, Simon McVittie)
46 | 
47 |   * Update Github Actions configuration (#658, Simon McVittie)
48 | 
49 | ----
50 | 
51 | See also <https://github.com/containers/bubblewrap/releases>
52 | 


--------------------------------------------------------------------------------
/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](https://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:
 96 | 
 97 | ```sh
 98 | meson _builddir
 99 | meson compile -C _builddir
100 | meson test -C _builddir
101 | meson install -C _builddir
102 | ```
103 | 
104 | Usage
105 | -----
106 | 
107 | bubblewrap works by creating a new, completely empty, mount
108 | namespace where the root is on a tmpfs that is invisible from the
109 | host, and will be automatically cleaned up when the last process
110 | exits. You can then use commandline options to construct the root
111 | filesystem and process environment and command to run in the
112 | namespace.
113 | 
114 | There's a larger [demo script](./demos/bubblewrap-shell.sh) in the
115 | source code, but here's a trimmed down version which runs
116 | a new shell reusing the host's `/usr`.
117 | 
118 | ```
119 | bwrap \
120 |     --ro-bind /usr /usr \
121 |     --symlink usr/lib64 /lib64 \
122 |     --proc /proc \
123 |     --dev /dev \
124 |     --unshare-pid \
125 |     --new-session \
126 |     bash
127 | ```
128 | 
129 | This is an incomplete example, but useful for purposes of
130 | illustration.  More often, rather than creating a container using the
131 | host's filesystem tree, you want to target a chroot.  There, rather
132 | than creating the symlink `lib64 -> usr/lib64` in the tmpfs, you might
133 | have already created it in the target rootfs.
134 | 
135 | Sandboxing
136 | ----------
137 | 
138 | The goal of bubblewrap is to run an application in a sandbox, where it
139 | has restricted access to parts of the operating system or user data
140 | such as the home directory.
141 | 
142 | bubblewrap always creates a new mount namespace, and the user can specify
143 | exactly what parts of the filesystem should be visible in the sandbox.
144 | Any such directories you specify mounted `nodev` by default, and can be made readonly.
145 | 
146 | Additionally you can use these kernel features:
147 | 
148 | User namespaces ([CLONE_NEWUSER](https://linux.die.net/man/2/clone)): This hides all but the current uid and gid from the
149 | sandbox. You can also change what the value of uid/gid should be in the sandbox.
150 | 
151 | IPC namespaces ([CLONE_NEWIPC](https://linux.die.net/man/2/clone)): The sandbox will get its own copy of all the
152 | different forms of IPCs, like SysV shared memory and semaphores.
153 | 
154 | PID namespaces ([CLONE_NEWPID](https://linux.die.net/man/2/clone)): The sandbox will not see any processes outside the sandbox. Additionally, bubblewrap will run a trivial pid1 inside your container to handle the requirements of reaping children in the sandbox. This avoids what is known now as the [Docker pid 1 problem](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/).
155 | 
156 | 
157 | Network namespaces ([CLONE_NEWNET](https://linux.die.net/man/2/clone)): The sandbox will not see the network. Instead it will have its own network namespace with only a loopback device.
158 | 
159 | UTS namespace ([CLONE_NEWUTS](https://linux.die.net/man/2/clone)): The sandbox will have its own hostname.
160 | 
161 | 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).
162 | 
163 | Limitations
164 | -----------
165 | 
166 | As noted in the [Sandbox security](#sandbox-security) section above,
167 | the level of protection between the sandboxed processes and the host system
168 | is entirely determined by the arguments passed to bubblewrap.
169 | Some aspects that require special care are noted here.
170 | 
171 | - If you are not filtering out `TIOCSTI` commands using seccomp filters,
172 | argument `--new-session` is needed to protect against out-of-sandbox
173 | command execution
174 | (see [CVE-2017-5226](https://github.com/containers/bubblewrap/issues/142)).
175 | 
176 | - Everything mounted into the sandbox can potentially be used to escalate
177 | privileges.
178 | For example, if you bind a D-Bus socket into the sandbox, it can be used to
179 | execute commands via systemd. You can use
180 | [xdg-dbus-proxy](https://github.com/flatpak/xdg-dbus-proxy) to filter
181 | D-Bus communication.
182 | 
183 | - Some applications deploy their own sandboxing mechanisms, and these can be
184 | restricted by the constraints imposed by bubblewrap's sandboxing.
185 | For example, some web browsers which configure their child proccesses via
186 | seccomp to not have access to the filesystem. If you limit the syscalls and
187 | don't allow the seccomp syscall, a browser cannot apply these restrictions.
188 | Similarly, if these rules were compiled into a file that is not available in
189 | the sandbox, the browser cannot load these rules from this file and cannot
190 | apply these restrictions.
191 | 
192 | Related project comparison: Firejail
193 | ------------------------------------
194 | 
195 | [Firejail](https://github.com/netblue30/firejail/tree/HEAD/src/firejail)
196 | is similar to Flatpak before bubblewrap was split out in that it combines
197 | a setuid tool with a lot of desktop-specific sandboxing features.  For
198 | example, Firejail knows about Pulseaudio, whereas bubblewrap does not.
199 | 
200 | The bubblewrap authors believe it's much easier to audit a small
201 | setuid program, and keep features such as Pulseaudio filtering as an
202 | unprivileged process, as now occurs in Flatpak.
203 | 
204 | Also, @cgwalters thinks trying to
205 | [whitelist file paths](https://github.com/netblue30/firejail/blob/37a5a3545ef6d8d03dad8bbd888f53e13274c9e5/src/firejail/fs_whitelist.c#L176)
206 | is a bad idea given the myriad ways users have to manipulate paths,
207 | and the myriad ways in which system administrators may configure a
208 | system.  The bubblewrap approach is to only retain a few specific
209 | Linux capabilities such as `CAP_SYS_ADMIN`, but to always access the
210 | filesystem as the invoking uid.  This entirely closes
211 | [TOCTTOU attacks](https://cwe.mitre.org/data/definitions/367.html) and
212 | such.
213 | 
214 | Related project comparison: Sandstorm.io
215 | ----------------------------------------
216 | 
217 | [Sandstorm.io](https://sandstorm.io/) requires unprivileged user
218 | namespaces to set up its sandbox, though it could easily be adapted
219 | to operate in a setuid mode as well. @cgwalters believes their code is
220 | fairly good, but it could still make sense to unify on bubblewrap.
221 | However, @kentonv (of Sandstorm) feels that while this makes sense
222 | in principle, the switching cost outweighs the practical benefits for
223 | now. This decision could be re-evaluated in the future, but it is not
224 | being actively pursued today.
225 | 
226 | Related project comparison: runc/binctr
227 | ----------------------------------------
228 | 
229 | [runC](https://github.com/opencontainers/runc) is currently working on
230 | supporting [rootless containers](https://github.com/opencontainers/runc/pull/774),
231 | without needing `setuid` or any other privileges during installation of
232 | runC (using unprivileged user namespaces rather than `setuid`),
233 | creation, and management of containers. However, the standard mode of
234 | using runC is similar to [systemd nspawn](https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html)
235 | in that it is tooling intended to be invoked by root.
236 | 
237 | The bubblewrap authors believe that runc and systemd-nspawn are not
238 | designed to be made setuid, and are distant from supporting such a mode.
239 | However with rootless containers, runC will be able to fulfill certain usecases
240 | that bubblewrap supports (with the added benefit of being a standardised and
241 | complete OCI runtime).
242 | 
243 | [binctr](https://github.com/jfrazelle/binctr) is just a wrapper for
244 | runC, so inherits all of its design tradeoffs.
245 | 
246 | What's with the name?!
247 | ----------------------
248 | 
249 | The name bubblewrap was chosen to convey that this
250 | tool runs as the parent of the application (so wraps it in some sense) and creates
251 | a protective layer (the sandbox) around it.
252 | 
253 | ![](bubblewrap.jpg)
254 | 
255 | (Bubblewrap cat by [dancing_stupidity](https://www.flickr.com/photos/27549668@N03/))
256 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 <http://www.gnu.org/licenses/>.
 17 |  *
 18 |  */
 19 | 
 20 | #include "config.h"
 21 | 
 22 | #include <sys/mount.h>
 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 = TEMP_FAILURE_RETRY (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 |   if (bwrap_level_prefix)
564 |     fprintf (stderr, "<%d>", LOG_ERR);
565 | 
566 |   fprintf (stderr, "bwrap: ");
567 | 
568 |   va_start (args, format);
569 |   vfprintf (stderr, format, args);
570 |   va_end (args);
571 | 
572 |   message = bind_mount_result_to_string (res, failing_path, &want_errno);
573 |   fprintf (stderr, ": %s", message);
574 |   /* message is leaked, but we're exiting unsuccessfully anyway, so ignore */
575 | 
576 |   if (want_errno)
577 |     {
578 |       switch (res)
579 |         {
580 |           case BIND_MOUNT_ERROR_MOUNT:
581 |           case BIND_MOUNT_ERROR_REMOUNT_DEST:
582 |           case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT:
583 |             fprintf (stderr, ": %s", mount_strerror (saved_errno));
584 |             break;
585 | 
586 |           case BIND_MOUNT_ERROR_REALPATH_DEST:
587 |           case BIND_MOUNT_ERROR_REOPEN_DEST:
588 |           case BIND_MOUNT_ERROR_READLINK_DEST_PROC_FD:
589 |           case BIND_MOUNT_ERROR_FIND_DEST_MOUNT:
590 |           case BIND_MOUNT_SUCCESS:
591 |           default:
592 |             fprintf (stderr, ": %s", strerror (saved_errno));
593 |         }
594 |     }
595 | 
596 |   fprintf (stderr, "\n");
597 |   exit (1);
598 | }
599 | 


--------------------------------------------------------------------------------
/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 <http://www.gnu.org/licenses/>.
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 | 


--------------------------------------------------------------------------------
/bubblewrap.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/containers/bubblewrap/9ca3b05ec787acfb4b17bed37db5719fa777834f/bubblewrap.jpg


--------------------------------------------------------------------------------
/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 <<EOF
 13 | Usage: see source code
 14 | EOF
 15 |     exit "${1-2}"
 16 | }
 17 | 
 18 | opt_clang=
 19 | 
 20 | getopt_temp="help"
 21 | getopt_temp="$getopt_temp,clang"
 22 | 
 23 | getopt_temp="$(getopt -o '' --long "${getopt_temp}" -n "$0" -- "$@")"
 24 | eval set -- "$getopt_temp"
 25 | unset getopt_temp
 26 | 
 27 | while true; do
 28 |     case "$1" in
 29 |         (--clang)
 30 |             clang=yes
 31 |             shift
 32 |             ;;
 33 | 
 34 |         (--help)
 35 |             usage 0
 36 |             # not reached
 37 |             ;;
 38 | 
 39 |         (--)
 40 |             shift
 41 |             break
 42 |             ;;
 43 | 
 44 |         (*)
 45 |             echo 'Error parsing options' >&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 |         build-essential \
 60 |         docbook-xml \
 61 |         docbook-xsl \
 62 |         libcap-dev \
 63 |         libselinux1-dev \
 64 |         libtool \
 65 |         meson \
 66 |         pkg-config \
 67 |         python3 \
 68 |         xsltproc \
 69 |         ${NULL+}
 70 | 
 71 |     if [ -n "${opt_clang}" ]; then
 72 |         apt-get -y install clang
 73 |     fi
 74 | 
 75 |     exit 0
 76 | fi
 77 | 
 78 | if command -v yum; then
 79 |     yum -y install \
 80 |         'pkgconfig(libselinux)' \
 81 |         /usr/bin/eu-readelf \
 82 |         docbook-style-xsl \
 83 |         gcc \
 84 |         git \
 85 |         libasan \
 86 |         libcap-devel \
 87 |         libtool \
 88 |         libtsan \
 89 |         libubsan \
 90 |         libxslt \
 91 |         make \
 92 |         meson \
 93 |         redhat-rpm-config \
 94 |         rsync \
 95 |         ${NULL+}
 96 | 
 97 |     if [ -n "${opt_clang}" ]; then
 98 |         yum -y install clang
 99 |     fi
100 | 
101 |     exit 0
102 | fi
103 | 
104 | echo "Unknown distribution" >&2
105 | exit 1
106 | 
107 | # vim:set sw=4 sts=4 et:
108 | 


--------------------------------------------------------------------------------
/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 | 		--overlay
55 | 		--overlay-src
56 | 		--perms
57 | 		--proc
58 | 		--remount-ro
59 | 		--ro-bind
60 | 		--ro-overlay
61 | 		--seccomp
62 | 		--setenv
63 | 		--size
64 | 		--symlink
65 | 		--sync-fd
66 | 		--tmp-overlay
67 | 		--uid
68 | 		--unsetenv
69 | 		--userns-block-fd
70 | 	"
71 | 
72 |     if [[ "$cur" == -* ]]; then
73 |         COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
74 |     fi
75 | 
76 |     return 0
77 | }
78 | complete -F _bwrap bwrap
79 | 
80 | # vim:set ft=bash:
81 | 


--------------------------------------------------------------------------------
/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 |           'datadir', get_option('prefix') / get_option('datadir'),
17 |         ],
18 |       )
19 |     else
20 |       bash_completion_dir = bash_completion.get_pkgconfig_variable(
21 |         'completionsdir',
22 |         default: '',
23 |         define_variable: [
24 |           'datadir', get_option('prefix') / get_option('datadir'),
25 |         ],
26 |       )
27 |     endif
28 |   endif
29 | endif
30 | 
31 | if bash_completion_dir == ''
32 |   bash_completion_dir = get_option('datadir') / 'bash-completion' / 'completions'
33 | endif
34 | 
35 | install_data('bwrap', install_dir : bash_completion_dir)
36 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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<<EOF
62 | [Application]
63 | name=org.gnome.Weather
64 | runtime=runtime/org.gnome.Platform/x86_64/master
65 | EOF
66 | 


--------------------------------------------------------------------------------
/demos/flatpak.bpf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/containers/bubblewrap/9ca3b05ec787acfb4b17bed37db5719fa777834f/demos/flatpak.bpf


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
  1 | project(
  2 |   'bubblewrap',
  3 |   'c',
  4 |   version : '0.11.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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 <http://www.gnu.org/licenses/>.
 17 |  *
 18 |  */
 19 | 
 20 | #include "config.h"
 21 | 
 22 | #include <arpa/inet.h>
 23 | #include <net/if.h>
 24 | #include <netinet/in.h>
 25 | #include <linux/loop.h>
 26 | #include <linux/netlink.h>
 27 | #include <linux/rtnetlink.h>
 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 = TEMP_FAILURE_RETRY (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 = TEMP_FAILURE_RETRY (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 | 


--------------------------------------------------------------------------------
/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 <http://www.gnu.org/licenses/>.
17 |  *
18 |  */
19 | 
20 | #pragma once
21 | 
22 | void loopback_setup (void);
23 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/release-checklist.md:
--------------------------------------------------------------------------------
 1 | bubblewrap release checklist
 2 | ============================
 3 | 
 4 | * Collect release notes in `NEWS`
 5 | * Update version number in `meson.build` and release date in `NEWS`
 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 from `NEWS` 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/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 <walters@verbum.org>
 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)
#39; | 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 | 


--------------------------------------------------------------------------------
/tests/libtest.sh:
--------------------------------------------------------------------------------
  1 | # shellcheck shell=bash
  2 | 
  3 | # Source library for shell script tests.
  4 | # Add non-bubblewrap-specific code to libtest-core.sh instead.
  5 | #
  6 | # Copyright (C) 2017 Colin Walters <walters@verbum.org>
  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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 -c "%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: =
#39;
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
#39; caps.test; then
204 |         assert_not_file_has_content caps.test '^Current: =.*cap_kill.*+eip
#39;
205 |         assert_not_file_has_content caps.test '^Current: =.*cap_fowner.*+eip
#39;
206 |         assert_file_has_content caps.test '^Current: =.*cap_net_bind_service.*+eip
#39;
207 |     else
208 |         assert_file_has_content caps.test '^Current: =eip.*cap_kill.*-eip
#39;
209 |         assert_file_has_content caps.test '^Current: =eip.*cap_fowner.*-eip
#39;
210 |         assert_not_file_has_content caps.test '^Current: =.*cap_net_bind_service.*-eip
#39;
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 <<EOF
218 | #!/usr/bin/env python3
219 | import struct,fcntl,sys
220 | path = sys.argv[1]
221 | if sys.argv[2] == 'wait':
222 |   locktype = fcntl.F_SETLKW
223 | else:
224 |   locktype = fcntl.F_SETLK
225 | lockdata = struct.pack("hhqqhh", fcntl.F_WRLCK, 0, 0, 0, 0, 0)
226 | fd=open(sys.argv[1], 'a')
227 | try:
228 |   fcntl.fcntl(fd.fileno(), locktype, lockdata)
229 | except IOError as e:
230 |   sys.exit(1)
231 | sys.exit(0)
232 | EOF
233 | chmod a+x lockf-n.py
234 | touch lock
235 | 
236 | for die_with_parent_argv in "--die-with-parent" "--die-with-parent --unshare-pid"; do
237 |     # We have to loop here, because bwrap doesn't wait for the lock if
238 |     # another process is holding it. If we're unlucky, lockf-n.py will
239 |     # be holding it.
240 |     bash -c "while true; do $RUN ${die_with_parent_argv} --lock-file $(pwd)/lock sleep 1h; done" &
241 |     childshellpid=$!
242 | 
243 |     # Wait for lock to be taken (yes hacky)
244 |     for x in $(seq 10); do
245 |         if ./lockf-n.py ./lock nowait; then
246 |             sleep 1
247 |         else
248 |             break
249 |         fi
250 |     done
251 |     if ./lockf-n.py ./lock nowait; then
252 |         assert_not_reached "timed out waiting for lock"
253 |     fi
254 | 
255 |     # Kill the shell, which should kill bwrap (and the sleep)
256 |     kill -9 ${childshellpid}
257 |     # Lock file should be unlocked
258 |     ./lockf-n.py ./lock wait
259 |     ok "die with parent ${die_with_parent_argv}"
260 | done
261 | 
262 | printf '%s--dir\0/tmp/hello/world\0' '' > 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<test.args 4<test.args2 5<test.args3
266 | ok "we can parse arguments from a fd"
267 | 
268 | mkdir bin
269 | echo "#!/bin/sh" > 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
#39;
372 | command stat -c '%a' new-dir-set-perms > new-dir-permissions
373 | assert_file_has_content new-dir-permissions '^1741
#39;
374 | command stat -c '%a' dir-already-existed > dir-permissions
375 | assert_file_has_content dir-permissions '^710
#39;
376 | command stat -c '%a' dir-already-existed2 > dir-permissions
377 | assert_file_has_content dir-permissions '^754
#39;
378 | command stat -c '%a' dir-chmod > dir-permissions
379 | assert_file_has_content dir-permissions '^1755
#39;
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
#39;
412 | command stat -c '%a' parent-of-1777 > dir-permissions
413 | assert_file_has_content dir-permissions '^755
#39;
414 | command stat -c '%a' parent-of-0755 > dir-permissions
415 | assert_file_has_content dir-permissions '^755
#39;
416 | command stat -c '%a' parent-of-0644 > dir-permissions
417 | assert_file_has_content dir-permissions '^755
#39;
418 | command stat -c '%a' parent-of-0750 > dir-permissions
419 | assert_file_has_content dir-permissions '^750
#39;
420 | command stat -c '%a' parent-of-0710 > dir-permissions
421 | assert_file_has_content dir-permissions '^750
#39;
422 | command stat -c '%a' parent-of-0720 > dir-permissions
423 | assert_file_has_content dir-permissions '^750
#39;
424 | command stat -c '%a' parent-of-0640 > dir-permissions
425 | assert_file_has_content dir-permissions '^750
#39;
426 | command stat -c '%a' parent-of-0700 > dir-permissions
427 | assert_file_has_content dir-permissions '^700
#39;
428 | command stat -c '%a' parent-of-0600 > dir-permissions
429 | assert_file_has_content dir-permissions '^700
#39;
430 | command stat -c '%a' parent-of-0705 > dir-permissions
431 | assert_file_has_content dir-permissions '^705
#39;
432 | command stat -c '%a' parent-of-0604 > dir-permissions
433 | assert_file_has_content dir-permissions '^705
#39;
434 | command stat -c '%a' parent-of-0000 > dir-permissions
435 | assert_file_has_content dir-permissions '^700
#39;
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
#39;
447 | $RUN \
448 |     --tmpfs "$(pwd -P)" \
449 |     stat -c '%a' "$(pwd -P)" > dir-permissions
450 | assert_file_has_content dir-permissions '^755
#39;
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
#39;
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
#39;
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
#39;
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
#39;
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
#39;
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
#39;
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
#39;
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
#39;
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
#39;
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
#39;
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
#39;
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 | $RUN --chdir / --chdir / true > stdout 2>&1
575 | assert_file_has_content stdout '^bwrap: Only the last --chdir option will take effect
#39;
576 | ok "warning logged for redundant --chdir"
577 | 
578 | $RUN --level-prefix --chdir / --chdir / true > stdout 2>&1
579 | assert_file_has_content stdout '^<4>bwrap: Only the last --chdir option will take effect
#39;
580 | ok "--level-prefix"
581 | 
582 | if test -n "${bwrap_is_suid:-}"; then
583 |     ok_skip "no --overlay support"
584 |     ok_skip "no --overlay support"
585 |     ok_skip "no --tmp-overlay support"
586 |     ok_skip "no --ro-overlay support"
587 |     ok_skip "no --overlay-src support"
588 | else
589 |     mkdir lower1 lower2 upper work
590 |     printf 1 > lower1/a
591 |     printf 2 > lower1/b
592 |     printf 3 > lower2/b
593 |     printf 4 > upper/a
594 | 
595 |     # Check if unprivileged overlayfs is available
596 |     if ! unshare -rm mount -t overlay -o lowerdir=lower1,upperdir=upper,workdir=work,userxattr overlay lower2; then
597 |         ok_skip "no kernel support for unprivileged overlayfs"
598 |         ok_skip "no kernel support for unprivileged overlayfs"
599 |         ok_skip "no kernel support for unprivileged overlayfs"
600 |         ok_skip "no kernel support for unprivileged overlayfs"
601 |         ok_skip "no kernel support for unprivileged overlayfs"
602 |     else
603 | 
604 |         # Test --overlay
605 |         if $RUN --overlay upper work /tmp true 2>err.txt; then
606 |             assert_not_reached At least one --overlay-src not required
607 |         fi
608 |         assert_file_has_content err.txt "^bwrap: --overlay requires at least one --overlay-src"
609 |         $RUN --overlay-src lower1 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/a > stdout
610 |         assert_file_has_content stdout '^4
#39;
611 |         $RUN --overlay-src lower1 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/b > stdout
612 |         assert_file_has_content stdout '^2
#39;
613 |         $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/a > stdout
614 |         assert_file_has_content stdout '^4
#39;
615 |         $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z cat /tmp/x/y/z/b > stdout
616 |         assert_file_has_content stdout '^3
#39;
617 |         $RUN --overlay-src lower1 --overlay-src lower2 --overlay upper work /tmp/x/y/z sh -c 'printf 5 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout
618 |         assert_file_has_content stdout '^5
#39;
619 |         assert_file_has_content upper/b '^5
#39;
620 |         ok "--overlay"
621 | 
622 |         # Test --overlay path escaping
623 |         # Coincidentally, ":,\ is the face I make contemplating anyone who might
624 |         # need this functionality, not that that's going to stop me from supporting
625 |         # it.
626 |         mkdir 'lower ":,\' 'upper ":,\' 'work ":,\'
627 |         printf 1 > 'lower ":,\'/a
628 |         $RUN --overlay-src 'lower ":,\' --overlay 'upper ":,\' 'work ":,\' /tmp/x sh -c 'cat /tmp/x/a; printf 2 > /tmp/x/a; cat /tmp/x/a' > stdout
629 |         assert_file_has_content stdout '^12
#39;
630 |         assert_file_has_content 'lower ":,\'/a '^1
#39;
631 |         assert_file_has_content 'upper ":,\'/a '^2
#39;
632 |         ok "--overlay path escaping"
633 | 
634 |         # Test --tmp-overlay
635 |         printf 1 > lower1/a
636 |         printf 2 > lower1/b
637 |         printf 3 > lower2/b
638 |         if $RUN --tmp-overlay /tmp true 2>err.txt; then
639 |             assert_not_reached At least one --overlay-src not required
640 |         fi
641 |         assert_file_has_content err.txt "^bwrap: --tmp-overlay requires at least one --overlay-src"
642 |         $RUN --overlay-src lower1 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout
643 |         assert_file_has_content stdout '^1
#39;
644 |         $RUN --overlay-src lower1 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout
645 |         assert_file_has_content stdout '^2
#39;
646 |         $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout
647 |         assert_file_has_content stdout '^1
#39;
648 |         $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout
649 |         assert_file_has_content stdout '^3
#39;
650 |         $RUN --overlay-src lower1 --overlay-src lower2 --tmp-overlay /tmp/x/y/z sh -c 'printf 4 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout
651 |         assert_file_has_content stdout '^4
#39;
652 |         $RUN --overlay-src lower1 --tmp-overlay /tmp/x --overlay-src lower2 --tmp-overlay /tmp/y sh -c 'cat /tmp/x/b; printf 4 > /tmp/x/b; cat /tmp/x/b; cat /tmp/y/b' > stdout
653 |         assert_file_has_content stdout '^243
#39;
654 |         assert_file_has_content lower1/b '^2
#39;
655 |         assert_file_has_content lower2/b '^3
#39;
656 |         ok "--tmp-overlay"
657 | 
658 |         # Test --ro-overlay
659 |         printf 1 > lower1/a
660 |         printf 2 > lower1/b
661 |         printf 3 > lower2/b
662 |         if $RUN --ro-overlay /tmp true 2>err.txt; then
663 |             assert_not_reached At least two --overlay-src not required
664 |         fi
665 |         assert_file_has_content err.txt "^bwrap: --ro-overlay requires at least two --overlay-src"
666 |         if $RUN --overlay-src lower1 --ro-overlay /tmp true 2>err.txt; then
667 |             assert_not_reached At least two --overlay-src not required
668 |         fi
669 |         assert_file_has_content err.txt "^bwrap: --ro-overlay requires at least two --overlay-src"
670 |         $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z cat /tmp/x/y/z/a > stdout
671 |         assert_file_has_content stdout '^1
#39;
672 |         $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z cat /tmp/x/y/z/b > stdout
673 |         assert_file_has_content stdout '^3
#39;
674 |         $RUN --overlay-src lower1 --overlay-src lower2 --ro-overlay /tmp/x/y/z sh -c 'printf 4 > /tmp/x/y/z/b; cat /tmp/x/y/z/b' > stdout
675 |         assert_file_has_content stdout '^3
#39;
676 |         ok "--ro-overlay"
677 | 
678 |         # Test --overlay-src restrictions
679 |         if $RUN --overlay-src /tmp true 2>err.txt; then
680 |             assert_not_reached Trailing --overlay-src allowed
681 |         fi
682 |         assert_file_has_content err.txt "^bwrap: --overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay"
683 |         if $RUN --overlay-src /tmp --chdir / true 2>err.txt; then
684 |             assert_not_reached --overlay-src allowed to precede non-overlay options
685 |         fi
686 |         assert_file_has_content err.txt "^bwrap: --overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay"
687 |         ok "--overlay-src restrictions"
688 | 
689 |     fi
690 | fi
691 | 
692 | done_testing
693 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 <http://www.gnu.org/licenses/>.
 18 |  */
 19 | 
 20 | #include <stdarg.h>
 21 | #include <stdint.h>
 22 | #include <stdio.h>
 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 | static void
204 | test_string_builder (void)
205 | {
206 |   StringBuilder sb = {0};
207 | 
208 |   strappend (&sb, "aaa");
209 |   g_assert_cmpstr (sb.str, ==, "aaa");
210 |   strappend (&sb, "bbb");
211 |   g_assert_cmpstr (sb.str, ==, "aaabbb");
212 |   strappendf (&sb, "c%dc%s", 9, "x");
213 |   g_assert_cmpstr (sb.str, ==, "aaabbbc9cx");
214 |   strappend_escape_for_mount_options (&sb, "/path :,\\");
215 |   g_assert_cmpstr (sb.str, ==, "aaabbbc9cx/path \\:\\,\\\\");
216 |   strappend (&sb, "zzz");
217 |   g_assert_cmpstr (sb.str, ==, "aaabbbc9cx/path \\:\\,\\\\zzz");
218 | 
219 |   free (sb.str);
220 |   sb = (StringBuilder){0};
221 | 
222 |   strappend_escape_for_mount_options (&sb, "aaa");
223 |   g_assert_cmpstr (sb.str, ==, "aaa");
224 | 
225 |   free (sb.str);
226 |   sb = (StringBuilder){0};
227 | 
228 |   strappend_escape_for_mount_options (&sb, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
229 |   g_assert_cmpstr (sb.str, ==, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
230 | 
231 |   free (sb.str);
232 | }
233 | 
234 | int
235 | main (int argc UNUSED,
236 |       char **argv UNUSED)
237 | {
238 |   setvbuf (stdout, NULL, _IONBF, 0);
239 |   test_n_elements ();
240 |   test_strconcat ();
241 |   test_strconcat3 ();
242 |   test_has_prefix ();
243 |   test_has_path_prefix ();
244 |   test_string_builder ();
245 |   printf ("1..%u\n", test_number);
246 |   return 0;
247 | }
248 | 


--------------------------------------------------------------------------------
/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 <errno.h>
 16 | #include <stdio.h>
 17 | #include <string.h>
 18 | #include <unistd.h>
 19 | #include <sys/ioctl.h>
 20 | #include <sys/prctl.h>
 21 | #include <sys/socket.h>
 22 | #include <sys/syscall.h>
 23 | #include <sys/stat.h>
 24 | #include <sys/types.h>
 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/use-as-subproject/.gitignore:
--------------------------------------------------------------------------------
1 | /_build/
2 | /subprojects/
3 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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/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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/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 | 


--------------------------------------------------------------------------------
/uncrustify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | uncrustify -c uncrustify.cfg --no-backup `git ls-tree --name-only -r HEAD | grep \\\.[ch]



    
    

    
    

    
    
    
    

    
    
    
    




    

    
The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
3 | -------------------------------------------------------------------------------- /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 <http://www.gnu.org/licenses/>. 17 | * 18 | */ 19 | #include "config.h" 20 | 21 | #include "utils.h" 22 | #include <limits.h> 23 | #include <stdint.h> 24 | #include <sys/syscall.h> 25 | #include <sys/socket.h> 26 | #include <sys/param.h> 27 | #ifdef HAVE_SELINUX 28 | #include <selinux/selinux.h> 29 | #endif 30 | 31 | #ifndef HAVE_SELINUX_2_3 32 | /* libselinux older than 2.3 weren't const-correct */ 33 | #define setexeccon(x) setexeccon ((security_context_t) x) 34 | #define setfscreatecon(x) setfscreatecon ((security_context_t) x) 35 | #define security_check_context(x) security_check_context ((security_context_t) x) 36 | #endif 37 | 38 | bool bwrap_level_prefix = false; 39 | 40 | __attribute__((format(printf, 2, 0))) static void 41 | bwrap_logv (int severity, 42 | const char *format, 43 | va_list args, 44 | const char *detail) 45 | { 46 | if (bwrap_level_prefix) 47 | fprintf (stderr, "<%d>", severity); 48 | 49 | fprintf (stderr, "bwrap: "); 50 | vfprintf (stderr, format, args); 51 | 52 | if (detail != NULL) 53 | fprintf (stderr, ": %s", detail); 54 | 55 | fprintf (stderr, "\n"); 56 | } 57 | 58 | void 59 | bwrap_log (int severity, 60 | const char *format, ...) 61 | { 62 | va_list args; 63 | 64 | va_start (args, format); 65 | bwrap_logv (severity, format, args, NULL); 66 | va_end (args); 67 | } 68 | 69 | void 70 | die_with_error (const char *format, ...) 71 | { 72 | va_list args; 73 | int errsv; 74 | 75 | errsv = errno; 76 | 77 | va_start (args, format); 78 | bwrap_logv (LOG_ERR, format, args, strerror (errsv)); 79 | va_end (args); 80 | 81 | exit (1); 82 | } 83 | 84 | void 85 | die_with_mount_error (const char *format, ...) 86 | { 87 | va_list args; 88 | int errsv; 89 | 90 | errsv = errno; 91 | 92 | va_start (args, format); 93 | bwrap_logv (LOG_ERR, format, args, mount_strerror (errsv)); 94 | va_end (args); 95 | 96 | exit (1); 97 | } 98 | 99 | void 100 | die (const char *format, ...) 101 | { 102 | va_list args; 103 | 104 | va_start (args, format); 105 | bwrap_logv (LOG_ERR, format, args, NULL); 106 | va_end (args); 107 | 108 | exit (1); 109 | } 110 | 111 | void 112 | die_unless_label_valid (UNUSED const char *label) 113 | { 114 | #ifdef HAVE_SELINUX 115 | if (is_selinux_enabled () == 1) 116 | { 117 | if (security_check_context (label) < 0) 118 | die_with_error ("invalid label %s", label); 119 | return; 120 | } 121 | #endif 122 | die ("labeling not supported on this system"); 123 | } 124 | 125 | void 126 | die_oom (void) 127 | { 128 | fputs ("Out of memory\n", stderr); 129 | exit (1); 130 | } 131 | 132 | /* Fork, return in child, exiting the previous parent */ 133 | void 134 | fork_intermediate_child (void) 135 | { 136 | int pid = fork (); 137 | if (pid == -1) 138 | die_with_error ("Can't fork for --pidns"); 139 | 140 | /* Parent is an process not needed */ 141 | if (pid != 0) 142 | exit (0); 143 | } 144 | 145 | void * 146 | xmalloc (size_t size) 147 | { 148 | void *res = malloc (size); 149 | 150 | if (res == NULL) 151 | die_oom (); 152 | return res; 153 | } 154 | 155 | void * 156 | xcalloc (size_t nmemb, size_t size) 157 | { 158 | void *res = calloc (nmemb, size); 159 | 160 | if (res == NULL) 161 | die_oom (); 162 | return res; 163 | } 164 | 165 | void * 166 | xrealloc (void *ptr, size_t size) 167 | { 168 | void *res; 169 | 170 | assert (size != 0); 171 | 172 | res = realloc (ptr, size); 173 | 174 | if (res == NULL) 175 | die_oom (); 176 | return res; 177 | } 178 | 179 | char * 180 | xstrdup (const char *str) 181 | { 182 | char *res; 183 | 184 | assert (str != NULL); 185 | 186 | res = strdup (str); 187 | if (res == NULL) 188 | die_oom (); 189 | 190 | return res; 191 | } 192 | 193 | void 194 | strfreev (char **str_array) 195 | { 196 | if (str_array) 197 | { 198 | int i; 199 | 200 | for (i = 0; str_array[i] != NULL; i++) 201 | free (str_array[i]); 202 | 203 | free (str_array); 204 | } 205 | } 206 | 207 | /* Compares if str has a specific path prefix. This differs 208 | from a regular prefix in two ways. First of all there may 209 | be multiple slashes separating the path elements, and 210 | secondly, if a prefix is matched that has to be en entire 211 | path element. For instance /a/prefix matches /a/prefix/foo/bar, 212 | but not /a/prefixfoo/bar. */ 213 | bool 214 | has_path_prefix (const char *str, 215 | const char *prefix) 216 | { 217 | while (true) 218 | { 219 | /* Skip consecutive slashes to reach next path 220 | element */ 221 | while (*str == '/') 222 | str++; 223 | while (*prefix == '/') 224 | prefix++; 225 | 226 | /* No more prefix path elements? Done! */ 227 | if (*prefix == 0) 228 | return true; 229 | 230 | /* Compare path element */ 231 | while (*prefix != 0 && *prefix != '/') 232 | { 233 | if (*str != *prefix) 234 | return false; 235 | str++; 236 | prefix++; 237 | } 238 | 239 | /* Matched prefix path element, 240 | must be entire str path element */ 241 | if (*str != '/' && *str != 0) 242 | return false; 243 | } 244 | } 245 | 246 | bool 247 | path_equal (const char *path1, 248 | const char *path2) 249 | { 250 | while (true) 251 | { 252 | /* Skip consecutive slashes to reach next path 253 | element */ 254 | while (*path1 == '/') 255 | path1++; 256 | while (*path2 == '/') 257 | path2++; 258 | 259 | /* No more prefix path elements? Done! */ 260 | if (*path1 == 0 || *path2 == 0) 261 | return *path1 == 0 && *path2 == 0; 262 | 263 | /* Compare path element */ 264 | while (*path1 != 0 && *path1 != '/') 265 | { 266 | if (*path1 != *path2) 267 | return false; 268 | path1++; 269 | path2++; 270 | } 271 | 272 | /* Matched path1 path element, must be entire path element */ 273 | if (*path2 != '/' && *path2 != 0) 274 | return false; 275 | } 276 | } 277 | 278 | 279 | bool 280 | has_prefix (const char *str, 281 | const char *prefix) 282 | { 283 | return strncmp (str, prefix, strlen (prefix)) == 0; 284 | } 285 | 286 | void 287 | xclearenv (void) 288 | { 289 | if (clearenv () != 0) 290 | die_with_error ("clearenv failed"); 291 | } 292 | 293 | void 294 | xsetenv (const char *name, const char *value, int overwrite) 295 | { 296 | if (setenv (name, value, overwrite)) 297 | die ("setenv failed"); 298 | } 299 | 300 | void 301 | xunsetenv (const char *name) 302 | { 303 | if (unsetenv (name)) 304 | die ("unsetenv failed"); 305 | } 306 | 307 | char * 308 | strconcat (const char *s1, 309 | const char *s2) 310 | { 311 | size_t len = 0; 312 | char *res; 313 | 314 | if (s1) 315 | len += strlen (s1); 316 | if (s2) 317 | len += strlen (s2); 318 | 319 | res = xmalloc (len + 1); 320 | *res = 0; 321 | if (s1) 322 | strcat (res, s1); 323 | if (s2) 324 | strcat (res, s2); 325 | 326 | return res; 327 | } 328 | 329 | char * 330 | strconcat3 (const char *s1, 331 | const char *s2, 332 | const char *s3) 333 | { 334 | size_t len = 0; 335 | char *res; 336 | 337 | if (s1) 338 | len += strlen (s1); 339 | if (s2) 340 | len += strlen (s2); 341 | if (s3) 342 | len += strlen (s3); 343 | 344 | res = xmalloc (len + 1); 345 | *res = 0; 346 | if (s1) 347 | strcat (res, s1); 348 | if (s2) 349 | strcat (res, s2); 350 | if (s3) 351 | strcat (res, s3); 352 | 353 | return res; 354 | } 355 | 356 | char * 357 | xasprintf (const char *format, 358 | ...) 359 | { 360 | char *buffer = NULL; 361 | va_list args; 362 | 363 | va_start (args, format); 364 | if (vasprintf (&buffer, format, args) == -1) 365 | die_oom (); 366 | va_end (args); 367 | 368 | return buffer; 369 | } 370 | 371 | int 372 | fdwalk (int proc_fd, int (*cb)(void *data, 373 | int fd), void *data) 374 | { 375 | int open_max; 376 | int fd; 377 | int dfd; 378 | int res = 0; 379 | DIR *d; 380 | 381 | dfd = TEMP_FAILURE_RETRY (openat (proc_fd, "self/fd", O_DIRECTORY | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY)); 382 | if (dfd == -1) 383 | return res; 384 | 385 | if ((d = fdopendir (dfd))) 386 | { 387 | struct dirent *de; 388 | 389 | while ((de = readdir (d))) 390 | { 391 | long l; 392 | char *e = NULL; 393 | 394 | if (de->d_name[0] == '.') 395 | continue; 396 | 397 | errno = 0; 398 | l = strtol (de->d_name, &e, 10); 399 | if (errno != 0 || !e || *e) 400 | continue; 401 | 402 | fd = (int) l; 403 | 404 | if ((long) fd != l) 405 | continue; 406 | 407 | if (fd == dirfd (d)) 408 | continue; 409 | 410 | if ((res = cb (data, fd)) != 0) 411 | break; 412 | } 413 | 414 | closedir (d); 415 | return res; 416 | } 417 | 418 | open_max = sysconf (_SC_OPEN_MAX); 419 | 420 | for (fd = 0; fd < open_max; fd++) 421 | if ((res = cb (data, fd)) != 0) 422 | break; 423 | 424 | return res; 425 | } 426 | 427 | /* Sets errno on error (!= 0), ENOSPC on short write */ 428 | int 429 | write_to_fd (int fd, 430 | const char *content, 431 | ssize_t len) 432 | { 433 | ssize_t res; 434 | 435 | while (len > 0) 436 | { 437 | res = write (fd, content, len); 438 | if (res < 0 && errno == EINTR) 439 | continue; 440 | if (res <= 0) 441 | { 442 | if (res == 0) /* Unexpected short write, should not happen when writing to a file */ 443 | errno = ENOSPC; 444 | return -1; 445 | } 446 | len -= res; 447 | content += res; 448 | } 449 | 450 | return 0; 451 | } 452 | 453 | /* Sets errno on error (!= 0), ENOSPC on short write */ 454 | int 455 | write_file_at (int dfd, 456 | const char *path, 457 | const char *content) 458 | { 459 | int fd; 460 | bool res; 461 | int errsv; 462 | 463 | fd = TEMP_FAILURE_RETRY (openat (dfd, path, O_RDWR | O_CLOEXEC, 0)); 464 | if (fd == -1) 465 | return -1; 466 | 467 | res = 0; 468 | if (content) 469 | res = write_to_fd (fd, content, strlen (content)); 470 | 471 | errsv = errno; 472 | close (fd); 473 | errno = errsv; 474 | 475 | return res; 476 | } 477 | 478 | /* Sets errno on error (!= 0), ENOSPC on short write */ 479 | int 480 | create_file (const char *path, 481 | mode_t mode, 482 | const char *content) 483 | { 484 | int fd; 485 | int res; 486 | int errsv; 487 | 488 | fd = TEMP_FAILURE_RETRY (creat (path, mode)); 489 | if (fd == -1) 490 | return -1; 491 | 492 | res = 0; 493 | if (content) 494 | res = write_to_fd (fd, content, strlen (content)); 495 | 496 | errsv = errno; 497 | close (fd); 498 | errno = errsv; 499 | 500 | return res; 501 | } 502 | 503 | int 504 | ensure_file (const char *path, 505 | mode_t mode) 506 | { 507 | struct stat buf; 508 | 509 | /* We check this ahead of time, otherwise 510 | the create file will fail in the read-only 511 | case with EROFS instead of EEXIST. 512 | 513 | We're trying to set up a mount point for a non-directory, so any 514 | non-directory, non-symlink is acceptable - it doesn't necessarily 515 | have to be a regular file. */ 516 | if (stat (path, &buf) == 0 && 517 | !S_ISDIR (buf.st_mode) && 518 | !S_ISLNK (buf.st_mode)) 519 | return 0; 520 | 521 | if (create_file (path, mode, NULL) != 0 && errno != EEXIST) 522 | return -1; 523 | 524 | return 0; 525 | } 526 | 527 | 528 | #define BUFSIZE 8192 529 | /* Sets errno on error (!= 0), ENOSPC on short write */ 530 | int 531 | copy_file_data (int sfd, 532 | int dfd) 533 | { 534 | char buffer[BUFSIZE]; 535 | ssize_t bytes_read; 536 | 537 | while (true) 538 | { 539 | bytes_read = read (sfd, buffer, BUFSIZE); 540 | if (bytes_read == -1) 541 | { 542 | if (errno == EINTR) 543 | continue; 544 | 545 | return -1; 546 | } 547 | 548 | if (bytes_read == 0) 549 | break; 550 | 551 | if (write_to_fd (dfd, buffer, bytes_read) != 0) 552 | return -1; 553 | } 554 | 555 | return 0; 556 | } 557 | 558 | /* Sets errno on error (!= 0), ENOSPC on short write */ 559 | int 560 | copy_file (const char *src_path, 561 | const char *dst_path, 562 | mode_t mode) 563 | { 564 | int sfd; 565 | int dfd; 566 | int res; 567 | int errsv; 568 | 569 | sfd = TEMP_FAILURE_RETRY (open (src_path, O_CLOEXEC | O_RDONLY)); 570 | if (sfd == -1) 571 | return -1; 572 | 573 | dfd = TEMP_FAILURE_RETRY (creat (dst_path, mode)); 574 | if (dfd == -1) 575 | { 576 | errsv = errno; 577 | close (sfd); 578 | errno = errsv; 579 | return -1; 580 | } 581 | 582 | res = copy_file_data (sfd, dfd); 583 | 584 | errsv = errno; 585 | close (sfd); 586 | close (dfd); 587 | errno = errsv; 588 | 589 | return res; 590 | } 591 | 592 | /* Sets errno on error (== NULL), 593 | * Always ensures terminating zero */ 594 | char * 595 | load_file_data (int fd, 596 | size_t *size) 597 | { 598 | cleanup_free char *data = NULL; 599 | ssize_t data_read; 600 | ssize_t data_len; 601 | ssize_t res; 602 | 603 | data_read = 0; 604 | data_len = 4080; 605 | data = xmalloc (data_len); 606 | 607 | do 608 | { 609 | if (data_len == data_read + 1) 610 | { 611 | if (data_len > SSIZE_MAX / 2) 612 | { 613 | errno = EFBIG; 614 | return NULL; 615 | } 616 | 617 | data_len *= 2; 618 | data = xrealloc (data, data_len); 619 | } 620 | 621 | do 622 | res = read (fd, data + data_read, data_len - data_read - 1); 623 | while (res < 0 && errno == EINTR); 624 | 625 | if (res < 0) 626 | return NULL; 627 | 628 | data_read += res; 629 | } 630 | while (res > 0); 631 | 632 | data[data_read] = 0; 633 | 634 | if (size) 635 | *size = (size_t) data_read; 636 | 637 | return steal_pointer (&data); 638 | } 639 | 640 | /* Sets errno on error (== NULL), 641 | * Always ensures terminating zero */ 642 | char * 643 | load_file_at (int dfd, 644 | const char *path) 645 | { 646 | int fd; 647 | char *data; 648 | int errsv; 649 | 650 | fd = TEMP_FAILURE_RETRY (openat (dfd, path, O_CLOEXEC | O_RDONLY)); 651 | if (fd == -1) 652 | return NULL; 653 | 654 | data = load_file_data (fd, NULL); 655 | 656 | errsv = errno; 657 | close (fd); 658 | errno = errsv; 659 | 660 | return data; 661 | } 662 | 663 | /* Sets errno on error (< 0) */ 664 | int 665 | get_file_mode (const char *pathname) 666 | { 667 | struct stat buf; 668 | 669 | if (stat (pathname, &buf) != 0) 670 | return -1; 671 | 672 | return buf.st_mode & S_IFMT; 673 | } 674 | 675 | int 676 | ensure_dir (const char *path, 677 | mode_t mode) 678 | { 679 | struct stat buf; 680 | 681 | /* We check this ahead of time, otherwise 682 | the mkdir call can fail in the read-only 683 | case with EROFS instead of EEXIST on some 684 | filesystems (such as NFS) */ 685 | if (stat (path, &buf) == 0) 686 | { 687 | if (!S_ISDIR (buf.st_mode)) 688 | { 689 | errno = ENOTDIR; 690 | return -1; 691 | } 692 | 693 | return 0; 694 | } 695 | 696 | if (mkdir (path, mode) == -1 && errno != EEXIST) 697 | return -1; 698 | 699 | return 0; 700 | } 701 | 702 | 703 | /* Sets errno on error (!= 0) */ 704 | int 705 | mkdir_with_parents (const char *pathname, 706 | mode_t mode, 707 | bool create_last) 708 | { 709 | cleanup_free char *fn = NULL; 710 | char *p; 711 | 712 | if (pathname == NULL || *pathname == '\0') 713 | { 714 | errno = EINVAL; 715 | return -1; 716 | } 717 | 718 | fn = xstrdup (pathname); 719 | 720 | p = fn; 721 | while (*p == '/') 722 | p++; 723 | 724 | do 725 | { 726 | while (*p && *p != '/') 727 | p++; 728 | 729 | if (!*p) 730 | p = NULL; 731 | else 732 | *p = '\0'; 733 | 734 | if (!create_last && p == NULL) 735 | break; 736 | 737 | if (ensure_dir (fn, mode) != 0) 738 | return -1; 739 | 740 | if (p) 741 | { 742 | *p++ = '/'; 743 | while (*p && *p == '/') 744 | p++; 745 | } 746 | } 747 | while (p); 748 | 749 | return 0; 750 | } 751 | 752 | /* Send an ucred with current pid/uid/gid over a socket, it can be 753 | read back with read_pid_from_socket(), and then the kernel has 754 | translated it between namespaces as needed. */ 755 | void 756 | send_pid_on_socket (int sockfd) 757 | { 758 | char buf[1] = { 0 }; 759 | struct msghdr msg = {}; 760 | struct iovec iov = { buf, sizeof (buf) }; 761 | const ssize_t control_len_snd = CMSG_SPACE(sizeof(struct ucred)); 762 | _Alignas(struct cmsghdr) char control_buf_snd[control_len_snd]; 763 | struct cmsghdr *cmsg; 764 | struct ucred cred; 765 | 766 | msg.msg_iov = &iov; 767 | msg.msg_iovlen = 1; 768 | msg.msg_control = control_buf_snd; 769 | msg.msg_controllen = control_len_snd; 770 | 771 | cmsg = CMSG_FIRSTHDR(&msg); 772 | cmsg->cmsg_level = SOL_SOCKET; 773 | cmsg->cmsg_type = SCM_CREDENTIALS; 774 | cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); 775 | 776 | cred.pid = getpid (); 777 | cred.uid = geteuid (); 778 | cred.gid = getegid (); 779 | memcpy (CMSG_DATA (cmsg), &cred, sizeof (cred)); 780 | 781 | if (TEMP_FAILURE_RETRY (sendmsg (sockfd, &msg, 0)) < 0) 782 | die_with_error ("Can't send pid"); 783 | } 784 | 785 | void 786 | create_pid_socketpair (int sockets[2]) 787 | { 788 | int enable = 1; 789 | 790 | if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) 791 | die_with_error ("Can't create intermediate pids socket"); 792 | 793 | if (setsockopt (sockets[0], SOL_SOCKET, SO_PASSCRED, &enable, sizeof (enable)) < 0) 794 | die_with_error ("Can't set SO_PASSCRED"); 795 | } 796 | 797 | int 798 | read_pid_from_socket (int sockfd) 799 | { 800 | char recv_buf[1] = { 0 }; 801 | struct msghdr msg = {}; 802 | struct iovec iov = { recv_buf, sizeof (recv_buf) }; 803 | const ssize_t control_len_rcv = CMSG_SPACE(sizeof(struct ucred)); 804 | _Alignas(struct cmsghdr) char control_buf_rcv[control_len_rcv]; 805 | struct cmsghdr* cmsg; 806 | 807 | msg.msg_iov = &iov; 808 | msg.msg_iovlen = 1; 809 | msg.msg_control = control_buf_rcv; 810 | msg.msg_controllen = control_len_rcv; 811 | 812 | if (TEMP_FAILURE_RETRY (recvmsg (sockfd, &msg, 0)) < 0) 813 | die_with_error ("Can't read pid from socket"); 814 | 815 | if (msg.msg_controllen <= 0) 816 | die ("Unexpected short read from pid socket"); 817 | 818 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) 819 | { 820 | const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); 821 | if (cmsg->cmsg_level == SOL_SOCKET && 822 | cmsg->cmsg_type == SCM_CREDENTIALS && 823 | payload_len == sizeof(struct ucred)) 824 | { 825 | struct ucred cred; 826 | 827 | memcpy (&cred, CMSG_DATA (cmsg), sizeof (cred)); 828 | return cred.pid; 829 | } 830 | } 831 | die ("No pid returned on socket"); 832 | } 833 | 834 | /* Sets errno on error (== NULL), 835 | * Always ensures terminating zero */ 836 | char * 837 | readlink_malloc (const char *pathname) 838 | { 839 | size_t size = 50; 840 | ssize_t n; 841 | cleanup_free char *value = NULL; 842 | 843 | do 844 | { 845 | if (size > SIZE_MAX / 2) 846 | die ("Symbolic link target pathname too long"); 847 | size *= 2; 848 | value = xrealloc (value, size); 849 | n = readlink (pathname, value, size - 1); 850 | if (n < 0) 851 | return NULL; 852 | } 853 | while (size - 2 < (size_t)n); 854 | 855 | value[n] = 0; 856 | return steal_pointer (&value); 857 | } 858 | 859 | char * 860 | get_oldroot_path (const char *path) 861 | { 862 | while (*path == '/') 863 | path++; 864 | return strconcat ("/oldroot/", path); 865 | } 866 | 867 | char * 868 | get_newroot_path (const char *path) 869 | { 870 | while (*path == '/') 871 | path++; 872 | return strconcat ("/newroot/", path); 873 | } 874 | 875 | int 876 | raw_clone (unsigned long flags, 877 | void *child_stack) 878 | { 879 | #if defined(__s390__) || defined(__CRIS__) 880 | /* On s390 and cris the order of the first and second arguments 881 | * of the raw clone() system call is reversed. */ 882 | return (int) syscall (__NR_clone, child_stack, flags); 883 | #else 884 | return (int) syscall (__NR_clone, flags, child_stack); 885 | #endif 886 | } 887 | 888 | int 889 | pivot_root (const char * new_root, const char * put_old) 890 | { 891 | #ifdef __NR_pivot_root 892 | return syscall (__NR_pivot_root, new_root, put_old); 893 | #else 894 | errno = ENOSYS; 895 | return -1; 896 | #endif 897 | } 898 | 899 | char * 900 | label_mount (const char *opt, UNUSED const char *mount_label) 901 | { 902 | #ifdef HAVE_SELINUX 903 | if (mount_label) 904 | { 905 | if (opt) 906 | return xasprintf ("%s,context=\"%s\"", opt, mount_label); 907 | else 908 | return xasprintf ("context=\"%s\"", mount_label); 909 | } 910 | #endif 911 | if (opt) 912 | return xstrdup (opt); 913 | return NULL; 914 | } 915 | 916 | int 917 | label_create_file (UNUSED const char *file_label) 918 | { 919 | #ifdef HAVE_SELINUX 920 | if (is_selinux_enabled () > 0 && file_label) 921 | return setfscreatecon (file_label); 922 | #endif 923 | return 0; 924 | } 925 | 926 | int 927 | label_exec (UNUSED const char *exec_label) 928 | { 929 | #ifdef HAVE_SELINUX 930 | if (is_selinux_enabled () > 0 && exec_label) 931 | return setexeccon (exec_label); 932 | #endif 933 | return 0; 934 | } 935 | 936 | /* 937 | * Like strerror(), but specialized for a failed mount(2) call. 938 | */ 939 | const char * 940 | mount_strerror (int errsv) 941 | { 942 | switch (errsv) 943 | { 944 | case ENOSPC: 945 | /* "No space left on device" misleads users into thinking there 946 | * is some sort of disk-space problem, but mount(2) uses that 947 | * errno value to mean something more like "limit exceeded". */ 948 | return ("Limit exceeded (ENOSPC). " 949 | "(Hint: Check that /proc/sys/fs/mount-max is sufficient, " 950 | "typically 100000)"); 951 | 952 | default: 953 | return strerror (errsv); 954 | } 955 | } 956 | 957 | /* 958 | * Return a + b if it would not overflow. 959 | * Die with an "out of memory" error if it would. 960 | */ 961 | static size_t 962 | xadd (size_t a, size_t b) 963 | { 964 | #if defined(__GNUC__) && __GNUC__ >= 5 965 | size_t result; 966 | if (__builtin_add_overflow (a, b, &result)) 967 | die_oom (); 968 | return result; 969 | #else 970 | if (a > SIZE_MAX - b) 971 | die_oom (); 972 | 973 | return a + b; 974 | #endif 975 | } 976 | 977 | /* 978 | * Return a * b if it would not overflow. 979 | * Die with an "out of memory" error if it would. 980 | */ 981 | static size_t 982 | xmul (size_t a, size_t b) 983 | { 984 | #if defined(__GNUC__) && __GNUC__ >= 5 985 | size_t result; 986 | if (__builtin_mul_overflow (a, b, &result)) 987 | die_oom (); 988 | return result; 989 | #else 990 | if (b != 0 && a > SIZE_MAX / b) 991 | die_oom (); 992 | 993 | return a * b; 994 | #endif 995 | } 996 | 997 | void 998 | strappend (StringBuilder *dest, const char *src) 999 | { 1000 | size_t len = strlen (src); 1001 | size_t new_offset = xadd (dest->offset, len); 1002 | 1003 | if (new_offset >= dest->size) 1004 | { 1005 | dest->size = xmul (xadd (new_offset, 1), 2); 1006 | dest->str = xrealloc (dest->str, dest->size); 1007 | } 1008 | 1009 | /* Preserves the invariant that dest->str is always null-terminated, even 1010 | * though the offset is positioned at the null byte for the next write. 1011 | */ 1012 | strncpy (dest->str + dest->offset, src, len + 1); 1013 | dest->offset = new_offset; 1014 | } 1015 | 1016 | __attribute__((format (printf, 2, 3))) 1017 | void 1018 | strappendf (StringBuilder *dest, const char *fmt, ...) 1019 | { 1020 | va_list args; 1021 | int len; 1022 | size_t new_offset; 1023 | 1024 | va_start (args, fmt); 1025 | len = vsnprintf (dest->str + dest->offset, dest->size - dest->offset, fmt, args); 1026 | va_end (args); 1027 | if (len < 0) 1028 | die_with_error ("vsnprintf"); 1029 | new_offset = xadd (dest->offset, len); 1030 | if (new_offset >= dest->size) 1031 | { 1032 | dest->size = xmul (xadd (new_offset, 1), 2); 1033 | dest->str = xrealloc (dest->str, dest->size); 1034 | va_start (args, fmt); 1035 | len = vsnprintf (dest->str + dest->offset, dest->size - dest->offset, fmt, args); 1036 | va_end (args); 1037 | if (len < 0) 1038 | die_with_error ("vsnprintf"); 1039 | } 1040 | 1041 | dest->offset = new_offset; 1042 | } 1043 | 1044 | void 1045 | strappend_escape_for_mount_options (StringBuilder *dest, const char *src) 1046 | { 1047 | bool unescaped = true; 1048 | 1049 | for (;;) 1050 | { 1051 | if (dest->offset == dest->size) 1052 | { 1053 | dest->size = MAX (64, xmul (dest->size, 2)); 1054 | dest->str = xrealloc (dest->str, dest->size); 1055 | } 1056 | switch (*src) 1057 | { 1058 | case '\0': 1059 | dest->str[dest->offset] = '\0'; 1060 | return; 1061 | 1062 | case '\\': 1063 | case ',': 1064 | case ':': 1065 | if (unescaped) 1066 | { 1067 | dest->str[dest->offset++] = '\\'; 1068 | unescaped = false; 1069 | continue; 1070 | } 1071 | /* else fall through */ 1072 | 1073 | default: 1074 | dest->str[dest->offset++] = *src; 1075 | unescaped = true; 1076 | break; 1077 | } 1078 | src++; 1079 | } 1080 | } 1081 | -------------------------------------------------------------------------------- /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 <http://www.gnu.org/licenses/>. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include <assert.h> 23 | #include <dirent.h> 24 | #include <errno.h> 25 | #include <fcntl.h> 26 | #include <stdarg.h> 27 | #include <stdbool.h> 28 | #include <stdio.h> 29 | #include <stdlib.h> 30 | #include <string.h> 31 | #include <syslog.h> 32 | #include <unistd.h> 33 | #include <sys/types.h> 34 | #include <sys/stat.h> 35 | 36 | #if 0 37 | #define debug(...) bwrap_log (LOG_DEBUG, __VA_ARGS__) 38 | #else 39 | #define debug(...) 40 | #endif 41 | 42 | #define UNUSED __attribute__((__unused__)) 43 | 44 | #define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) 45 | 46 | #ifndef TEMP_FAILURE_RETRY 47 | #define TEMP_FAILURE_RETRY(expression) \ 48 | (__extension__ \ 49 | ({ long int __result; \ 50 | do __result = (long int) (expression); \ 51 | while (__result == -1L && errno == EINTR); \ 52 | __result; })) 53 | #endif 54 | 55 | #define PIPE_READ_END 0 56 | #define PIPE_WRITE_END 1 57 | 58 | #ifndef PR_SET_CHILD_SUBREAPER 59 | #define PR_SET_CHILD_SUBREAPER 36 60 | #endif 61 | 62 | extern bool bwrap_level_prefix; 63 | 64 | void bwrap_log (int severity, 65 | const char *format, 66 | ...) __attribute__((format (printf, 2, 3))); 67 | #define warn(...) bwrap_log (LOG_WARNING, __VA_ARGS__) 68 | 69 | void die_with_error (const char *format, 70 | ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); 71 | void die_with_mount_error (const char *format, 72 | ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); 73 | void die (const char *format, 74 | ...) __attribute__((__noreturn__)) __attribute__((format (printf, 1, 2))); 75 | void die_oom (void) __attribute__((__noreturn__)); 76 | void die_unless_label_valid (const char *label); 77 | 78 | void fork_intermediate_child (void); 79 | 80 | void *xmalloc (size_t size); 81 | void *xcalloc (size_t nmemb, size_t size); 82 | void *xrealloc (void *ptr, 83 | size_t size); 84 | char *xstrdup (const char *str); 85 | void strfreev (char **str_array); 86 | void xclearenv (void); 87 | void xsetenv (const char *name, 88 | const char *value, 89 | int overwrite); 90 | void xunsetenv (const char *name); 91 | char *strconcat (const char *s1, 92 | const char *s2); 93 | char *strconcat3 (const char *s1, 94 | const char *s2, 95 | const char *s3); 96 | char * xasprintf (const char *format, 97 | ...) __attribute__((format (printf, 1, 2))); 98 | bool has_prefix (const char *str, 99 | const char *prefix); 100 | bool has_path_prefix (const char *str, 101 | const char *prefix); 102 | bool path_equal (const char *path1, 103 | const char *path2); 104 | int fdwalk (int proc_fd, 105 | int (*cb)(void *data, 106 | int fd), 107 | void *data); 108 | char *load_file_data (int fd, 109 | size_t *size); 110 | char *load_file_at (int dirfd, 111 | const char *path); 112 | int write_file_at (int dirfd, 113 | const char *path, 114 | const char *content); 115 | int write_to_fd (int fd, 116 | const char *content, 117 | ssize_t len); 118 | int copy_file_data (int sfd, 119 | int dfd); 120 | int copy_file (const char *src_path, 121 | const char *dst_path, 122 | mode_t mode); 123 | int create_file (const char *path, 124 | mode_t mode, 125 | const char *content); 126 | int ensure_file (const char *path, 127 | mode_t mode); 128 | int ensure_dir (const char *path, 129 | mode_t mode); 130 | int get_file_mode (const char *pathname); 131 | int mkdir_with_parents (const char *pathname, 132 | mode_t mode, 133 | bool create_last); 134 | void create_pid_socketpair (int sockets[2]); 135 | void send_pid_on_socket (int socket); 136 | int read_pid_from_socket (int socket); 137 | char *get_oldroot_path (const char *path); 138 | char *get_newroot_path (const char *path); 139 | char *readlink_malloc (const char *pathname); 140 | 141 | /* syscall wrappers */ 142 | int raw_clone (unsigned long flags, 143 | void *child_stack); 144 | int pivot_root (const char *new_root, 145 | const char *put_old); 146 | char *label_mount (const char *opt, 147 | const char *mount_label); 148 | int label_exec (const char *exec_label); 149 | int label_create_file (const char *file_label); 150 | 151 | const char *mount_strerror (int errsv); 152 | 153 | static inline void 154 | cleanup_freep (void *p) 155 | { 156 | void **pp = (void **) p; 157 | 158 | if (*pp) 159 | free (*pp); 160 | } 161 | 162 | static inline void 163 | cleanup_strvp (void *p) 164 | { 165 | void **pp = (void **) p; 166 | 167 | strfreev (*pp); 168 | } 169 | 170 | static inline void 171 | cleanup_fdp (int *fdp) 172 | { 173 | int fd; 174 | 175 | assert (fdp); 176 | 177 | fd = *fdp; 178 | if (fd != -1) 179 | (void) close (fd); 180 | } 181 | 182 | #define cleanup_free __attribute__((cleanup (cleanup_freep))) 183 | #define cleanup_fd __attribute__((cleanup (cleanup_fdp))) 184 | #define cleanup_strv __attribute__((cleanup (cleanup_strvp))) 185 | 186 | static inline void * 187 | steal_pointer (void *pp) 188 | { 189 | void **ptr = (void **) pp; 190 | void *ref; 191 | 192 | ref = *ptr; 193 | *ptr = NULL; 194 | 195 | return ref; 196 | } 197 | 198 | /* type safety */ 199 | #define steal_pointer(pp) \ 200 | (0 ? (*(pp)) : (steal_pointer) (pp)) 201 | 202 | typedef struct _StringBuilder StringBuilder; 203 | 204 | struct _StringBuilder 205 | { 206 | char * str; 207 | size_t size; 208 | size_t offset; 209 | }; 210 | 211 | void strappend (StringBuilder *dest, 212 | const char *src); 213 | void strappendf (StringBuilder *dest, 214 | const char *fmt, 215 | ...); 216 | void strappend_escape_for_mount_options (StringBuilder *dest, 217 | const char *src); 218 | --------------------------------------------------------------------------------