mirror of
https://github.com/google/sandboxed-api.git
synced 2024-03-22 13:11:30 +08:00
Sandboxed API OSS release.
PiperOrigin-RevId: 238996664 Change-Id: I9646527e2be68ee0b6b371572b7aafe967102e57 Signed-off-by: Christian Blichmann <cblichmann@google.com>
This commit is contained in:
commit
177b969e8c
4
.clang-format
Normal file
4
.clang-format
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: Google
|
||||||
|
...
|
25
CONTRIBUTING.md
Normal file
25
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Want to contribute? Great! First, read this page (including the small print at the end).
|
||||||
|
|
||||||
|
### Before you contribute
|
||||||
|
Before we can use your code, you must sign the
|
||||||
|
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
|
||||||
|
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||||
|
copyright to your changes, even after your contribution becomes part of our
|
||||||
|
codebase, so we need your permission to use and distribute your code. We also
|
||||||
|
need to be sure of various other things—for instance that you'll tell us if you
|
||||||
|
know that your code infringes on other people's patents. You don't have to sign
|
||||||
|
the CLA until after you've submitted your code for review and a member has
|
||||||
|
approved it, but you must do it before we can put your code into our codebase.
|
||||||
|
Before you start working on a larger contribution, you should get in touch with
|
||||||
|
us first through the issue tracker with your idea so that we can help out and
|
||||||
|
possibly guide you. Coordinating up front makes it much easier to avoid
|
||||||
|
frustration later on.
|
||||||
|
|
||||||
|
### Code reviews
|
||||||
|
All submissions, including submissions by project members, require review. We
|
||||||
|
use Github pull requests for this purpose.
|
||||||
|
|
||||||
|
### The small print
|
||||||
|
Contributions made by corporations are covered by a different agreement than
|
||||||
|
the one above, the
|
||||||
|
[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).
|
202
LICENSE
Normal file
202
LICENSE
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
96
README.md
Normal file
96
README.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# Sandboxed API
|
||||||
|
|
||||||
|
Copyright 2019 Google LLC
|
||||||
|
|
||||||
|
![Sandbox](sandboxed_api/docs/images/playing-in-sand.png)
|
||||||
|
|
||||||
|
|
||||||
|
## What is Sandboxed API?
|
||||||
|
|
||||||
|
The Sandboxed API project (**SAPI**) aims to make sandboxing of C/C++ libraries
|
||||||
|
less burdensome: after initial setup of security policies and generation of
|
||||||
|
library interfaces, an almost-identical stub API is generated (using a
|
||||||
|
[templated based programming variable hierarchy system](sandboxed_api/docs/variables.md)),
|
||||||
|
transparently forwarding calls using a custom RPC layer to the real library
|
||||||
|
running inside a sandboxed environment.
|
||||||
|
|
||||||
|
Additionally, each SAPI library utilizes a tightly defined security policy, in
|
||||||
|
contrast to typical sandboxed project, where security policies must cover total
|
||||||
|
syscall/resource footprint of all utilized libraries.
|
||||||
|
|
||||||
|
|
||||||
|
## Intended audience
|
||||||
|
|
||||||
|
SAPI is designed to help you sandbox only a part of binary. That is, a library
|
||||||
|
or some other code with an unknown security posture.
|
||||||
|
|
||||||
|
See [Sandboxing Code](sandboxed_api/docs/sandbox-overview.md) to make sure this is the type of
|
||||||
|
sandboxing you are looking for.
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
Navigate to our [How it works](sandboxed_api/docs/howitworks.md) page.
|
||||||
|
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Sandboxes available for use in Google required additional implementation work
|
||||||
|
with each new instance of project which was intended to be sandboxed, even if
|
||||||
|
it reused the same software library. Sandbox security policies and other
|
||||||
|
restrictions applied to the sandboxed process had to be reimplemented each
|
||||||
|
time, and data exchange mechanisms between trusted and untrusted parts of
|
||||||
|
the code had to be designed from the scratch.
|
||||||
|
|
||||||
|
While designing the Sandboxed API project, our goal was to make this process
|
||||||
|
easy and straightforward. Our working motto is: **Sandbox once, use anywhere**.
|
||||||
|
|
||||||
|
|
||||||
|
## Is it proven technology?
|
||||||
|
|
||||||
|
The project has been designed, developed and is maintained by members of
|
||||||
|
the Google Sandbox Team. It also uses our field-tested
|
||||||
|
[Sandbox 2](sandboxed_api/sandbox2/README.md).
|
||||||
|
|
||||||
|
Currently, many internal projects are already using SAPI to isolate
|
||||||
|
their production workloads. You can read more about them in the
|
||||||
|
[Examples](sandboxed_api/docs/examples.md) section.
|
||||||
|
|
||||||
|
We've also prepared some more example SAPI implementations for your reference.
|
||||||
|
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Install the required dependencies, this assumes you are running Debian 10
|
||||||
|
"Buster":
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "deb http://storage.googleapis.com/bazel-apt stable jdk1.8" | \
|
||||||
|
sudo tee /etc/apt/sources.list.d/bazel.list
|
||||||
|
wget -qO - https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
|
||||||
|
sudo apt-get install -qy python-typing python-clang-7 libclang-7-dev
|
||||||
|
sudo apt-get install -qy build-essential linux-libc-dev bazel
|
||||||
|
```
|
||||||
|
|
||||||
|
Clone and run the build:
|
||||||
|
```bash
|
||||||
|
git clone github.com/google/sandboxed-api && cd sandboxed-api
|
||||||
|
bazel build ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Try out one of the [examples](sandboxed_api/docs/examples.md):
|
||||||
|
```bash
|
||||||
|
bazel run //sandboxed_api/examples/stringop:main_stringop
|
||||||
|
```
|
||||||
|
|
||||||
|
There are also a more detailed instructions that should help you
|
||||||
|
**[getting started with SAPI](sandboxed_api/docs/getting-started.md)**.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Involved
|
||||||
|
|
||||||
|
If you want to contribute, please read [CONTRIBUTING.md](CONTRIBUTING.md) and
|
||||||
|
send us pull requests. You can also report bugs or file feature requests.
|
||||||
|
|
||||||
|
If you'd like to talk to the developers or get notified about major product
|
||||||
|
updates, you may want to subscribe to our
|
||||||
|
[mailing list](sandboxed-api-users@googlegroups.com).
|
143
WORKSPACE
Normal file
143
WORKSPACE
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
workspace(name = "com_google_sandboxed_api")
|
||||||
|
|
||||||
|
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
|
load("//sandboxed_api/bazel:repositories.bzl", "autotools_repository")
|
||||||
|
|
||||||
|
# Abseil
|
||||||
|
http_archive(
|
||||||
|
name = "com_google_absl",
|
||||||
|
strip_prefix = "abseil-cpp-master",
|
||||||
|
urls = ["https://github.com/abseil/abseil-cpp/archive/master.zip"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Abseil-py
|
||||||
|
http_archive(
|
||||||
|
name = "com_google_absl_py",
|
||||||
|
strip_prefix = "abseil-py-master",
|
||||||
|
urls = ["https://github.com/abseil/abseil-py/archive/master.zip"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Abseil-py deps
|
||||||
|
http_archive(
|
||||||
|
name = "six_archive",
|
||||||
|
build_file = "//sandboxed_api:bazel/external/six.BUILD",
|
||||||
|
sha256 = "105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a",
|
||||||
|
strip_prefix = "six-1.10.0",
|
||||||
|
urls = [
|
||||||
|
"http://mirror.bazel.build/pypi.python.org/packages/source/s/six/six-1.10.0.tar.gz",
|
||||||
|
"https://pypi.python.org/packages/source/s/six/six-1.10.0.tar.gz",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
# NOTE: The name here is used in _enum_module.py to find the sys.path entry.
|
||||||
|
name = "enum34_archive",
|
||||||
|
build_file = "//sandboxed_api:bazel/external/enum34.BUILD",
|
||||||
|
sha256 = "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1",
|
||||||
|
urls = [
|
||||||
|
"https://mirror.bazel.build/pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz",
|
||||||
|
"https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# zlib
|
||||||
|
http_archive(
|
||||||
|
name = "net_zlib",
|
||||||
|
build_file = "//sandboxed_api:bazel/external/zlib.BUILD",
|
||||||
|
patch_args = ["-p1"],
|
||||||
|
# This is a patch that removes the "OF" macro that is used in zlib function
|
||||||
|
# definitions. It is necessary, because libclang, the library used by the
|
||||||
|
# interface generator to parse C/C++ files contains a bug that manifests
|
||||||
|
# itself with macros like this.
|
||||||
|
# We are investigating better ways to avoid this issue. For most "normal"
|
||||||
|
# C and C++ headers, parsing just works.
|
||||||
|
patches = ["//sandboxed_api:bazel/external/zlib.patch"],
|
||||||
|
sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1",
|
||||||
|
strip_prefix = "zlib-1.2.11",
|
||||||
|
urls = ["https://www.zlib.net/zlib-1.2.11.tar.gz"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# gflags
|
||||||
|
http_archive(
|
||||||
|
name = "com_github_gflags_gflags",
|
||||||
|
sha256 = "53b16091efa386ab11e33f018eef0ed489e0ab63554455293cbb0cc2a5f50e98",
|
||||||
|
strip_prefix = "gflags-28f50e0fed19872e0fd50dd23ce2ee8cd759338e",
|
||||||
|
urls = ["https://github.com/gflags/gflags/archive/28f50e0fed19872e0fd50dd23ce2ee8cd759338e.zip"], # 2019-01-25
|
||||||
|
)
|
||||||
|
|
||||||
|
# GoogleTest/GoogleMock
|
||||||
|
http_archive(
|
||||||
|
name = "com_google_googletest",
|
||||||
|
sha256 = "70404b4a887fd8efce2179e9918e58cdac03245e575408ed87799696e816ecb8",
|
||||||
|
strip_prefix = "googletest-f80d6644d4b451f568a2e7aea1e01e842eb242dc",
|
||||||
|
urls = ["https://github.com/google/googletest/archive/f80d6644d4b451f568a2e7aea1e01e842eb242dc.zip"], # 2019-02-05
|
||||||
|
)
|
||||||
|
|
||||||
|
# Google Benchmark
|
||||||
|
http_archive(
|
||||||
|
name = "com_google_benchmark",
|
||||||
|
strip_prefix = "benchmark-master",
|
||||||
|
urls = ["https://github.com/google/benchmark/archive/master.zip"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Google logging
|
||||||
|
http_archive(
|
||||||
|
name = "com_google_glog",
|
||||||
|
sha256 = "74010e549e3555a11d3eb22b80f0040fa4f013a4b254b2d5ede12afcc92e690b",
|
||||||
|
strip_prefix = "glog-41f4bf9cbc3e8995d628b459f6a239df43c2b84a",
|
||||||
|
urls = ["https://github.com/google/glog/archive/41f4bf9cbc3e8995d628b459f6a239df43c2b84a.zip"], # 2019-02-02
|
||||||
|
)
|
||||||
|
|
||||||
|
# Protobuf
|
||||||
|
http_archive(
|
||||||
|
name = "com_google_protobuf",
|
||||||
|
sha256 = "9510dd2afc29e7245e9e884336f848c8a6600a14ae726adb6befdb4f786f0be2",
|
||||||
|
strip_prefix = "protobuf-3.6.1.3",
|
||||||
|
urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.6.1.3.zip"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# libcap
|
||||||
|
http_archive(
|
||||||
|
name = "org_kernel_libcap",
|
||||||
|
build_file = "//sandboxed_api:bazel/external/libcap.BUILD",
|
||||||
|
sha256 = "ef83108f77314e50bae926ae473f9b130b15240d17cbae05089e19c36a8676d6",
|
||||||
|
strip_prefix = "libcap-2.13",
|
||||||
|
urls = ["https://www.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-2.13.tar.gz"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# libffi
|
||||||
|
autotools_repository(
|
||||||
|
name = "org_sourceware_libffi",
|
||||||
|
build_file = "//sandboxed_api:bazel/external/libffi.BUILD",
|
||||||
|
sha256 = "403d67aabf1c05157855ea2b1d9950263fb6316536c8c333f5b9ab1eb2f20ecf",
|
||||||
|
strip_prefix = "libffi-3.3-rc0",
|
||||||
|
urls = ["https://github.com/libffi/libffi/releases/download/v3.3-rc0/libffi-3.3-rc0.tar.gz"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# libunwind
|
||||||
|
autotools_repository(
|
||||||
|
name = "org_gnu_libunwind",
|
||||||
|
build_file = "//sandboxed_api:bazel/external/libunwind.BUILD",
|
||||||
|
configure_args = [
|
||||||
|
"--disable-documentation",
|
||||||
|
"--disable-shared",
|
||||||
|
"--enable-ptrace",
|
||||||
|
],
|
||||||
|
sha256 = "3f3ecb90e28cbe53fba7a4a27ccce7aad188d3210bb1964a923a731a27a75acb",
|
||||||
|
strip_prefix = "libunwind-1.2.1",
|
||||||
|
urls = ["https://github.com/libunwind/libunwind/releases/download/v1.2.1/libunwind-1.2.1.tar.gz"], # v1.2.1
|
||||||
|
)
|
170
sandboxed_api/BUILD.bazel
Normal file
170
sandboxed_api/BUILD.bazel
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
exports_files(["LICENSE"])
|
||||||
|
|
||||||
|
load("//sandboxed_api/bazel:proto.bzl", "sapi_proto_library")
|
||||||
|
|
||||||
|
sapi_proto_library(
|
||||||
|
name = "proto_arg",
|
||||||
|
srcs = ["proto_arg.proto"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# The main Sandboxed-API library
|
||||||
|
cc_library(
|
||||||
|
name = "sapi",
|
||||||
|
srcs = [
|
||||||
|
"embed_file.cc",
|
||||||
|
"file_toc.h",
|
||||||
|
"sandbox.cc",
|
||||||
|
"transaction.cc",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"embed_file.h",
|
||||||
|
"sandbox.h",
|
||||||
|
"transaction.h",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":vars",
|
||||||
|
"//sandboxed_api/sandbox2",
|
||||||
|
"//sandboxed_api/sandbox2:client",
|
||||||
|
"//sandboxed_api/sandbox2:comms",
|
||||||
|
"//sandboxed_api/sandbox2:util",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/sandbox2/util:file_base",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/sandbox2/util:runfiles",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"@com_google_absl//absl/base",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/container:flat_hash_map",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
"@com_google_absl//absl/synchronization",
|
||||||
|
"@com_google_absl//absl/time",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Definitions shared between sandboxee and master used for higher-level IPC.
|
||||||
|
cc_library(
|
||||||
|
name = "call",
|
||||||
|
hdrs = ["call.h"],
|
||||||
|
deps = [
|
||||||
|
":var_type",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "lenval_core",
|
||||||
|
hdrs = ["lenval_core.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "var_type",
|
||||||
|
hdrs = ["var_type.h"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Variable hierarchy
|
||||||
|
cc_library(
|
||||||
|
name = "vars",
|
||||||
|
srcs = [
|
||||||
|
"rpcchannel.cc",
|
||||||
|
"var_abstract.cc",
|
||||||
|
"var_int.cc",
|
||||||
|
"var_lenval.cc",
|
||||||
|
"var_pointable.cc",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"proto_helper.h",
|
||||||
|
"rpcchannel.h",
|
||||||
|
"var_abstract.h",
|
||||||
|
"var_array.h",
|
||||||
|
"var_int.h",
|
||||||
|
"var_lenval.h",
|
||||||
|
"var_pointable.h",
|
||||||
|
"var_proto.h",
|
||||||
|
"var_ptr.h",
|
||||||
|
"var_reg.h",
|
||||||
|
"var_struct.h",
|
||||||
|
"var_void.h",
|
||||||
|
"vars.h",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":call",
|
||||||
|
":lenval_core",
|
||||||
|
":proto_arg_cc",
|
||||||
|
":var_type",
|
||||||
|
"//sandboxed_api/sandbox2:comms",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:statusor",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
"@com_google_absl//absl/synchronization",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# A stub to be linked in with SAPI libraries
|
||||||
|
cc_library(
|
||||||
|
name = "client",
|
||||||
|
srcs = ["client.cc"],
|
||||||
|
# TODO(124852776): investigate whether this is necessary
|
||||||
|
linkopts = [
|
||||||
|
"-Wl,-ldl",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":call",
|
||||||
|
":lenval_core",
|
||||||
|
":proto_arg_cc",
|
||||||
|
":vars",
|
||||||
|
"//sandboxed_api/sandbox2:client",
|
||||||
|
"//sandboxed_api/sandbox2:comms",
|
||||||
|
"//sandboxed_api/sandbox2:forkingclient",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
"@com_google_protobuf//:protobuf",
|
||||||
|
"@org_sourceware_libffi//:libffi",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "sapi_test",
|
||||||
|
srcs = ["sapi_test.cc"],
|
||||||
|
tags = ["local"],
|
||||||
|
deps = [
|
||||||
|
":sapi",
|
||||||
|
"//sandboxed_api/examples/stringop/lib:stringop-sapi",
|
||||||
|
"//sandboxed_api/examples/stringop/lib:stringop_params_proto",
|
||||||
|
"//sandboxed_api/examples/sum/lib:sum-sapi",
|
||||||
|
"//sandboxed_api/examples/sum/lib:sum-sapi_embed",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_benchmark//:benchmark",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
60
sandboxed_api/bazel/BUILD
Normal file
60
sandboxed_api/bazel/BUILD
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache v2.0
|
||||||
|
|
||||||
|
exports_files([
|
||||||
|
"proto.bzl",
|
||||||
|
"embed_data.bzl",
|
||||||
|
"sapi.bzl",
|
||||||
|
])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"//sandboxed_api/bazel:embed_data.bzl",
|
||||||
|
"sapi_cc_embed_data",
|
||||||
|
)
|
||||||
|
|
||||||
|
# An implicit dependency of all "sapi_cc_embed_data" rules that builds
|
||||||
|
# embedded data into .cc files.
|
||||||
|
cc_binary(
|
||||||
|
name = "filewrapper",
|
||||||
|
srcs = ["filewrapper.cc"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_cc_embed_data(
|
||||||
|
name = "filewrapper_embedded",
|
||||||
|
srcs = ["testdata/filewrapper_embedded.bin"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "filewrapper_test",
|
||||||
|
srcs = ["filewrapper_test.cc"],
|
||||||
|
data = ["testdata/filewrapper_embedded.bin"],
|
||||||
|
deps = [
|
||||||
|
":filewrapper_embedded",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"//sandboxed_api/sandbox2:testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:file_helpers",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
],
|
||||||
|
)
|
97
sandboxed_api/bazel/embed_data.bzl
Normal file
97
sandboxed_api/bazel/embed_data.bzl
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Embeds binary data in cc_*() rules."""
|
||||||
|
|
||||||
|
_FILEWRAPPER = "//sandboxed_api/bazel:filewrapper"
|
||||||
|
|
||||||
|
# TODO(cblichmann): Convert this is to use a "_cc_toolchain" once Bazel #4370
|
||||||
|
# is fixed.
|
||||||
|
def _sapi_cc_embed_data_impl(ctx):
|
||||||
|
cc_file_artifact = None
|
||||||
|
h_file_artifact = None
|
||||||
|
for output in ctx.outputs.outs:
|
||||||
|
if output.path.endswith(".h"):
|
||||||
|
h_file_artifact = output
|
||||||
|
elif output.path.endswith(".cc") or output.path.endswith(".cpp"):
|
||||||
|
cc_file_artifact = output
|
||||||
|
|
||||||
|
args = ctx.actions.args()
|
||||||
|
args.add(ctx.label.package)
|
||||||
|
args.add(ctx.attr.ident)
|
||||||
|
args.add(ctx.attr.namespace if ctx.attr.namespace else "")
|
||||||
|
args.add(h_file_artifact)
|
||||||
|
args.add(cc_file_artifact)
|
||||||
|
args.add_all(ctx.files.srcs)
|
||||||
|
|
||||||
|
ctx.actions.run(
|
||||||
|
executable = ctx.executable._filewrapper,
|
||||||
|
inputs = ctx.files.srcs,
|
||||||
|
outputs = [h_file_artifact, cc_file_artifact],
|
||||||
|
arguments = [args],
|
||||||
|
mnemonic = "CcEmbedData",
|
||||||
|
progress_message = (
|
||||||
|
"Creating sapi_cc_embed_data file for {}".format(ctx.label)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
_sapi_cc_embed_data = rule(
|
||||||
|
implementation = _sapi_cc_embed_data_impl,
|
||||||
|
output_to_genfiles = True,
|
||||||
|
attrs = {
|
||||||
|
"srcs": attr.label_list(
|
||||||
|
allow_files = True,
|
||||||
|
),
|
||||||
|
"namespace": attr.string(),
|
||||||
|
"ident": attr.string(),
|
||||||
|
"_filewrapper": attr.label(
|
||||||
|
executable = True,
|
||||||
|
cfg = "host",
|
||||||
|
allow_files = True,
|
||||||
|
default = Label(_FILEWRAPPER),
|
||||||
|
),
|
||||||
|
"outs": attr.output_list(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def sapi_cc_embed_data(name, srcs = [], namespace = "", **kwargs):
|
||||||
|
"""Embeds arbitrary binary data in cc_*() rules.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name for this rule.
|
||||||
|
srcs: A list of files to be embedded.
|
||||||
|
namespace: C++ namespace to wrap the generated types in.
|
||||||
|
**kwargs: extra arguments like testonly, visibility, etc.
|
||||||
|
"""
|
||||||
|
embed_rule = "_%s_sapi" % name
|
||||||
|
_sapi_cc_embed_data(
|
||||||
|
name = embed_rule,
|
||||||
|
srcs = srcs,
|
||||||
|
namespace = namespace,
|
||||||
|
ident = name,
|
||||||
|
outs = [
|
||||||
|
"%s.h" % name,
|
||||||
|
"%s.cc" % name,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
native.cc_library(
|
||||||
|
name = name,
|
||||||
|
hdrs = [":%s.h" % name],
|
||||||
|
srcs = [":%s.cc" % name],
|
||||||
|
deps = [
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
**kwargs
|
||||||
|
)
|
27
sandboxed_api/bazel/external/enum34.BUILD
vendored
Normal file
27
sandboxed_api/bazel/external/enum34.BUILD
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Description:
|
||||||
|
# enum34 provides a backport of the enum module for Python 2.
|
||||||
|
|
||||||
|
licenses(["notice"]) # MIT
|
||||||
|
|
||||||
|
exports_files(["LICENSE"])
|
||||||
|
|
||||||
|
py_library(
|
||||||
|
name = "enum",
|
||||||
|
srcs = ["enum34-1.1.6/enum/__init__.py"],
|
||||||
|
srcs_version = "PY2AND3",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
85
sandboxed_api/bazel/external/libcap.BUILD
vendored
Normal file
85
sandboxed_api/bazel/external/libcap.BUILD
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
genrule(
|
||||||
|
name = "cap_names_sed",
|
||||||
|
srcs = ["libcap/include/linux/capability.h"],
|
||||||
|
outs = ["cap_names.sed"],
|
||||||
|
# Use the same logic as libcap/Makefile
|
||||||
|
cmd = """
|
||||||
|
sed -ne '/^#define[ \\t]CAP[_A-Z]\+[ \\t]\+[0-9]\+/{s/^#define \([^ \\t]*\)[ \\t]*\([^ \\t]*\)/\{\"\\1\",\\2\},/p;}' $< | \\
|
||||||
|
tr \"[:upper:]\" \"[:lower:]\" > $@
|
||||||
|
""",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "makenames_textual_hdrs",
|
||||||
|
textual_hdrs = [
|
||||||
|
"cap_names.sed",
|
||||||
|
"libcap/include/linux/capability.h",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_binary(
|
||||||
|
name = "makenames",
|
||||||
|
srcs = [
|
||||||
|
"libcap/_makenames.c",
|
||||||
|
"libcap/include/sys/capability.h",
|
||||||
|
],
|
||||||
|
includes = [
|
||||||
|
"libcap/..",
|
||||||
|
"libcap/include",
|
||||||
|
"libcap/include/uapi",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [":makenames_textual_hdrs"],
|
||||||
|
)
|
||||||
|
|
||||||
|
genrule(
|
||||||
|
name = "cap_names_h",
|
||||||
|
outs = ["libcap/cap_names.h"],
|
||||||
|
cmd = "mkdir -p libcap && $(location makenames) > $@ || { rm -f $@; false; }",
|
||||||
|
tools = [":makenames"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "libcap",
|
||||||
|
srcs = [
|
||||||
|
"libcap/cap_alloc.c",
|
||||||
|
"libcap/cap_extint.c",
|
||||||
|
"libcap/cap_file.c",
|
||||||
|
"libcap/cap_flag.c",
|
||||||
|
"libcap/cap_proc.c",
|
||||||
|
"libcap/cap_text.c",
|
||||||
|
],
|
||||||
|
copts = [
|
||||||
|
"-Wno-tautological-compare",
|
||||||
|
"-Wno-unused-result",
|
||||||
|
# Work around sys/xattr.h not declaring this
|
||||||
|
"-DXATTR_NAME_CAPS=\"\\\"security.capability\\\"\"",
|
||||||
|
],
|
||||||
|
includes = [
|
||||||
|
"libcap",
|
||||||
|
"libcap/include",
|
||||||
|
],
|
||||||
|
textual_hdrs = [
|
||||||
|
"libcap/include/sys/capability.h",
|
||||||
|
"libcap/libcap.h",
|
||||||
|
"libcap/cap_names.h",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
53
sandboxed_api/bazel/external/libffi.BUILD
vendored
Normal file
53
sandboxed_api/bazel/external/libffi.BUILD
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "libffi",
|
||||||
|
srcs = [
|
||||||
|
"configure-bazel-gen/fficonfig.h",
|
||||||
|
"configure-bazel-gen/include/ffi.h",
|
||||||
|
"configure-bazel-gen/include/ffitarget.h",
|
||||||
|
"include/ffi_cfi.h",
|
||||||
|
"include/ffi_common.h",
|
||||||
|
"src/closures.c",
|
||||||
|
"src/debug.c",
|
||||||
|
"src/java_raw_api.c",
|
||||||
|
"src/prep_cif.c",
|
||||||
|
"src/raw_api.c",
|
||||||
|
"src/types.c",
|
||||||
|
"src/x86/asmnames.h",
|
||||||
|
"src/x86/ffi.c",
|
||||||
|
"src/x86/ffi64.c",
|
||||||
|
"src/x86/ffiw64.c",
|
||||||
|
"src/x86/internal.h",
|
||||||
|
"src/x86/internal64.h",
|
||||||
|
"src/x86/sysv.S",
|
||||||
|
"src/x86/unix64.S",
|
||||||
|
"src/x86/win64.S",
|
||||||
|
],
|
||||||
|
copts = [
|
||||||
|
# libffi-3.3-rc0 uses variable length arrays for closures on all
|
||||||
|
# platforms.
|
||||||
|
"-Wno-vla",
|
||||||
|
# libffi does not check the result of ftruncate.
|
||||||
|
"-Wno-unused-result",
|
||||||
|
],
|
||||||
|
includes = [
|
||||||
|
"configure-bazel-gen",
|
||||||
|
"configure-bazel-gen/include",
|
||||||
|
"include",
|
||||||
|
],
|
||||||
|
textual_hdrs = ["src/dlmalloc.c"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
164
sandboxed_api/bazel/external/libunwind.BUILD
vendored
Normal file
164
sandboxed_api/bazel/external/libunwind.BUILD
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
LIBUNWIND_COPTS = [
|
||||||
|
"-DHAVE_CONFIG_H",
|
||||||
|
"-D_GNU_SOURCE",
|
||||||
|
"-Iexternal/org_gnu_libunwind/include",
|
||||||
|
"-Iexternal/org_gnu_libunwind/include/tdep",
|
||||||
|
"-Iexternal/org_gnu_libunwind/src",
|
||||||
|
]
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "internal_headers",
|
||||||
|
srcs = [
|
||||||
|
"include/compiler.h",
|
||||||
|
"include/config.h",
|
||||||
|
"include/dwarf.h",
|
||||||
|
"include/dwarf-eh.h",
|
||||||
|
"include/dwarf_i.h",
|
||||||
|
"include/libunwind.h",
|
||||||
|
"include/libunwind-common.h",
|
||||||
|
"include/libunwind-coredump.h",
|
||||||
|
"include/libunwind-dynamic.h",
|
||||||
|
"include/libunwind-ptrace.h",
|
||||||
|
"include/libunwind-x86_64.h",
|
||||||
|
"include/libunwind_i.h",
|
||||||
|
"include/mempool.h",
|
||||||
|
"include/remote.h",
|
||||||
|
"include/tdep-x86_64/dwarf-config.h",
|
||||||
|
"include/tdep-x86_64/libunwind_i.h",
|
||||||
|
"include/tdep/dwarf-config.h",
|
||||||
|
"include/tdep/libunwind_i.h",
|
||||||
|
"include/unwind.h",
|
||||||
|
"src/elf32.h",
|
||||||
|
"src/elf64.h",
|
||||||
|
"src/elfxx.h",
|
||||||
|
"src/os-linux.h",
|
||||||
|
"src/x86_64/init.h",
|
||||||
|
"src/x86_64/offsets.h",
|
||||||
|
"src/x86_64/ucontext_i.h",
|
||||||
|
"src/x86_64/unwind_i.h",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Header-only library for included source files.
|
||||||
|
cc_library(
|
||||||
|
name = "included_sources",
|
||||||
|
srcs = [
|
||||||
|
"src/elf64.h",
|
||||||
|
"src/elfxx.h",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"src/elfxx.c", # Included by elf32.c/elf64.c
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "sources_common",
|
||||||
|
srcs = [
|
||||||
|
"src/dwarf/Gexpr.c",
|
||||||
|
"src/dwarf/Gfde.c",
|
||||||
|
"src/dwarf/Gfind_proc_info-lsb.c",
|
||||||
|
"src/dwarf/Gfind_unwind_table.c",
|
||||||
|
"src/dwarf/Gparser.c",
|
||||||
|
"src/dwarf/Gpe.c",
|
||||||
|
"src/dwarf/Gstep.c",
|
||||||
|
"src/dwarf/global.c",
|
||||||
|
"src/mi/Gdestroy_addr_space.c",
|
||||||
|
"src/mi/Gdyn-extract.c",
|
||||||
|
"src/mi/Gfind_dynamic_proc_info.c",
|
||||||
|
"src/mi/Gget_accessors.c",
|
||||||
|
"src/mi/Gget_proc_name.c",
|
||||||
|
"src/mi/Gget_reg.c",
|
||||||
|
"src/mi/Gput_dynamic_unwind_info.c",
|
||||||
|
"src/mi/flush_cache.c",
|
||||||
|
"src/mi/init.c",
|
||||||
|
"src/mi/mempool.c",
|
||||||
|
"src/os-linux.c",
|
||||||
|
"src/x86_64/Gcreate_addr_space.c",
|
||||||
|
"src/x86_64/Gglobal.c",
|
||||||
|
"src/x86_64/Ginit.c",
|
||||||
|
"src/x86_64/Gos-linux.c",
|
||||||
|
"src/x86_64/Gregs.c",
|
||||||
|
"src/x86_64/Gresume.c",
|
||||||
|
"src/x86_64/Gstash_frame.c",
|
||||||
|
"src/x86_64/Gstep.c",
|
||||||
|
"src/x86_64/is_fpreg.c",
|
||||||
|
"src/x86_64/setcontext.S",
|
||||||
|
":internal_headers",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "sources_ptrace",
|
||||||
|
srcs = ["src/x86_64/Ginit_remote.c"],
|
||||||
|
)
|
||||||
|
|
||||||
|
[cc_library(
|
||||||
|
name = "unwind-ptrace" + ("-wrapped" if do_wrap else ""),
|
||||||
|
srcs = [
|
||||||
|
"src/mi/Gdyn-remote.c",
|
||||||
|
"src/ptrace/_UPT_access_fpreg.c",
|
||||||
|
"src/ptrace/_UPT_access_mem.c",
|
||||||
|
"src/ptrace/_UPT_access_reg.c",
|
||||||
|
"src/ptrace/_UPT_accessors.c",
|
||||||
|
"src/ptrace/_UPT_create.c",
|
||||||
|
"src/ptrace/_UPT_destroy.c",
|
||||||
|
"src/ptrace/_UPT_elf.c",
|
||||||
|
"src/ptrace/_UPT_find_proc_info.c",
|
||||||
|
"src/ptrace/_UPT_get_dyn_info_list_addr.c",
|
||||||
|
"src/ptrace/_UPT_get_proc_name.c",
|
||||||
|
"src/ptrace/_UPT_internal.h",
|
||||||
|
"src/ptrace/_UPT_put_unwind_info.c",
|
||||||
|
"src/ptrace/_UPT_reg_offset.c",
|
||||||
|
"src/ptrace/_UPT_resume.c",
|
||||||
|
":internal_headers",
|
||||||
|
":sources_common",
|
||||||
|
":sources_ptrace",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"include/config.h",
|
||||||
|
"include/libunwind.h",
|
||||||
|
],
|
||||||
|
copts = LIBUNWIND_COPTS + [
|
||||||
|
# Assume our inferior doesn't have frame pointers, regardless of
|
||||||
|
# whether we ourselves do or not.
|
||||||
|
"-DNO_FRAME_POINTER",
|
||||||
|
"-fno-common",
|
||||||
|
# Ignore warning in src/ptrace/_UPT_get_dyn_info_list_addr.c
|
||||||
|
"-Wno-cpp",
|
||||||
|
] + (
|
||||||
|
["-D{symbol}={symbol}_wrapped".format(symbol = symbol) for symbol in [
|
||||||
|
"ptrace",
|
||||||
|
"_UPT_create",
|
||||||
|
"_UPT_accessors",
|
||||||
|
"_UPT_destroy",
|
||||||
|
"_Ux86_64_create_addr_space",
|
||||||
|
"_Ux86_64_destroy_addr_space",
|
||||||
|
"_Ux86_64_init_remote",
|
||||||
|
"_Ux86_64_step",
|
||||||
|
"_Ux86_64_get_proc_name",
|
||||||
|
"_Ux86_64_get_reg",
|
||||||
|
]] if do_wrap else []
|
||||||
|
),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [":included_sources"],
|
||||||
|
# This forces a link failure in any target that depends on both
|
||||||
|
# unwind-ptrace and unwind-ptrace-wrapped.
|
||||||
|
alwayslink = 1,
|
||||||
|
) for do_wrap in [
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
]]
|
23
sandboxed_api/bazel/external/six.BUILD
vendored
Normal file
23
sandboxed_api/bazel/external/six.BUILD
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Six provides simple utilities for wrapping over differences between Python 2
|
||||||
|
# and Python 3.
|
||||||
|
|
||||||
|
py_library(
|
||||||
|
name = "six",
|
||||||
|
srcs = ["six.py"],
|
||||||
|
srcs_version = "PY2AND3",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
56
sandboxed_api/bazel/external/zlib.BUILD
vendored
Normal file
56
sandboxed_api/bazel/external/zlib.BUILD
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # BSD/MIT-like license
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "zlib",
|
||||||
|
srcs = [
|
||||||
|
"adler32.c",
|
||||||
|
"compress.c",
|
||||||
|
"crc32.c",
|
||||||
|
"deflate.c",
|
||||||
|
"gzclose.c",
|
||||||
|
"gzlib.c",
|
||||||
|
"gzread.c",
|
||||||
|
"gzwrite.c",
|
||||||
|
"infback.c",
|
||||||
|
"inffast.c",
|
||||||
|
"inflate.c",
|
||||||
|
"inflate.h",
|
||||||
|
"inftrees.c",
|
||||||
|
"trees.c",
|
||||||
|
"trees.h",
|
||||||
|
"uncompr.c",
|
||||||
|
"zutil.c",
|
||||||
|
],
|
||||||
|
hdrs = ["zlib.h"],
|
||||||
|
# Use -Dverbose=-1 to turn off zlib's trace logging. (#3280)
|
||||||
|
copts = [
|
||||||
|
"-w",
|
||||||
|
"-Dverbose=-1",
|
||||||
|
],
|
||||||
|
includes = ["."],
|
||||||
|
textual_hdrs = [
|
||||||
|
"crc32.h",
|
||||||
|
"deflate.h",
|
||||||
|
"gzguts.h",
|
||||||
|
"inffast.h",
|
||||||
|
"inffixed.h",
|
||||||
|
"inftrees.h",
|
||||||
|
"zconf.h",
|
||||||
|
"zutil.h",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
2118
sandboxed_api/bazel/external/zlib.patch
vendored
Normal file
2118
sandboxed_api/bazel/external/zlib.patch
vendored
Normal file
File diff suppressed because it is too large
Load Diff
281
sandboxed_api/bazel/filewrapper.cc
Normal file
281
sandboxed_api/bazel/filewrapper.cc
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Simple utility to wrap a binary file in a C++ source file.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/strings/ascii.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_replace.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/fileops.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/strerror.h"
|
||||||
|
#include "sandboxed_api/util/raw_logging.h"
|
||||||
|
|
||||||
|
// C-escapes a character and writes it to a file stream.
|
||||||
|
void FWriteCEscapedC(int c, FILE* out) {
|
||||||
|
/* clang-format off */
|
||||||
|
constexpr char kCEscapedLen[256] = {
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 4, 4, 2, 4, 4, // \t, \n, \r
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // "
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // '0'..'9'
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 'A'..'O'
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, // 'P'..'Z', '\'
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 'a'..'o'
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, // 'p'..'z', DEL
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
};
|
||||||
|
/* clang-format on */
|
||||||
|
|
||||||
|
int char_len = kCEscapedLen[c];
|
||||||
|
if (char_len == 1) {
|
||||||
|
fputc(c, out);
|
||||||
|
} else if (char_len == 2) {
|
||||||
|
fputc('\\', out);
|
||||||
|
switch (c) {
|
||||||
|
case '\0':
|
||||||
|
fputc('0', out);
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
fputc('n', out);
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
fputc('r', out);
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
fputc('t', out);
|
||||||
|
break;
|
||||||
|
case '\"':
|
||||||
|
fputc('\"', out);
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
fputc('\'', out);
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
fputc('\\', out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fputc('\\', out);
|
||||||
|
fputc('0' + c / 64, out);
|
||||||
|
fputc('0' + (c % 64) / 8, out);
|
||||||
|
fputc('0' + c % 8, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small RAII class that wraps C-style FILE streams and sets up buffering.
|
||||||
|
class File {
|
||||||
|
public:
|
||||||
|
File(const char* name, const char* mode)
|
||||||
|
: name_{name}, stream_{fopen(name, mode)}, buf_(4096, '\0') {
|
||||||
|
SAPI_RAW_PCHECK(stream_ != nullptr, "Open %s", name_);
|
||||||
|
std::setvbuf(stream_, &buf_[0], _IOFBF, buf_.size());
|
||||||
|
Check();
|
||||||
|
}
|
||||||
|
~File() { fclose(stream_); }
|
||||||
|
|
||||||
|
void Check() { SAPI_RAW_PCHECK(!ferror(stream_), "I/O on %s", name_); }
|
||||||
|
|
||||||
|
FILE* get() const { return stream_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* name_;
|
||||||
|
FILE* stream_;
|
||||||
|
std::string buf_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format literals for generating the .h file
|
||||||
|
constexpr const char kHFileHeaderFmt[] =
|
||||||
|
R"(// Automatically generated by sapi_cc_embed_data() Bazel rule
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_FILE_TOC_H_
|
||||||
|
#define SANDBOXED_API_FILE_TOC_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
struct FileToc {
|
||||||
|
const char* name;
|
||||||
|
const char* data;
|
||||||
|
size_t size;
|
||||||
|
// Not actually used/computed by sapi_cc_embed_data(), this is for
|
||||||
|
// compatibility with legacy code.
|
||||||
|
unsigned char md5digest[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_FILE_TOC_H_
|
||||||
|
|
||||||
|
#ifndef %3$s
|
||||||
|
#define %3$s
|
||||||
|
|
||||||
|
)";
|
||||||
|
constexpr const char kHNamespaceBeginFmt[] =
|
||||||
|
R"(namespace %s {
|
||||||
|
)";
|
||||||
|
constexpr const char kHFileTocDefsFmt[] =
|
||||||
|
R"(
|
||||||
|
const FileToc* %1$s_create();
|
||||||
|
size_t %1$s_size();
|
||||||
|
)";
|
||||||
|
constexpr const char kHNamespaceEndFmt[] =
|
||||||
|
R"(
|
||||||
|
} // namespace %s
|
||||||
|
)";
|
||||||
|
constexpr const char kHFileFooterFmt[] =
|
||||||
|
R"(
|
||||||
|
#endif // %s
|
||||||
|
)";
|
||||||
|
|
||||||
|
// Format literals for generating the .cc file out of the input files.
|
||||||
|
constexpr const char kCcFileHeaderFmt[] =
|
||||||
|
R"(// Automatically generated by sapi_cc_embed_data() Bazel rule
|
||||||
|
|
||||||
|
#include "%s/%s.h"
|
||||||
|
|
||||||
|
#include "absl/base/macros.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
|
||||||
|
)";
|
||||||
|
constexpr const char kCcNamespaceBeginFmt[] =
|
||||||
|
R"(namespace %s {
|
||||||
|
|
||||||
|
)";
|
||||||
|
constexpr const char kCcDataBeginFmt[] =
|
||||||
|
R"(constexpr absl::string_view %s = {")";
|
||||||
|
constexpr const char kCcDataEndFmt[] =
|
||||||
|
R"(", %d};
|
||||||
|
)";
|
||||||
|
constexpr const char kCcFileTocDefsBegin[] =
|
||||||
|
R"(
|
||||||
|
constexpr FileToc kToc[] = {
|
||||||
|
)";
|
||||||
|
constexpr const char kCcFileTocDefsEntryFmt[] =
|
||||||
|
R"( {"%1$s", %2$s.data(), %2$s.size(), {}},
|
||||||
|
)";
|
||||||
|
constexpr const char kCcFileTocDefsEndFmt[] =
|
||||||
|
R"(
|
||||||
|
// Terminate array
|
||||||
|
{nullptr, nullptr, 0, {}},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FileToc* %1$s_create() {
|
||||||
|
return kToc;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t %1$s_size() {
|
||||||
|
return ABSL_ARRAYSIZE(kToc) - 1;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
constexpr const char kCcNamespaceEndFmt[] =
|
||||||
|
R"(
|
||||||
|
} // namespace %s
|
||||||
|
)";
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc < 7) {
|
||||||
|
// We're not aiming for human usability here, as this tool is always run as
|
||||||
|
// part of the build.
|
||||||
|
absl::FPrintF(stderr,
|
||||||
|
"%s PACKAGE NAME NAMESPACE OUTPUT_H OUTPUT_CC INPUT...",
|
||||||
|
argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
char** arg = &argv[1];
|
||||||
|
|
||||||
|
const char* package = *arg++;
|
||||||
|
--argc;
|
||||||
|
const char* name = *arg++;
|
||||||
|
std::string toc_ident = absl::StrReplaceAll(name, {{"-", "_"}});
|
||||||
|
--argc;
|
||||||
|
|
||||||
|
const char* ns = *arg++;
|
||||||
|
const bool have_ns = strlen(ns) > 0;
|
||||||
|
--argc;
|
||||||
|
|
||||||
|
{ // Write header file first.
|
||||||
|
File out_h(*arg++, "wb");
|
||||||
|
--argc;
|
||||||
|
std::string header_guard = absl::StrReplaceAll(
|
||||||
|
absl::AsciiStrToUpper(absl::StrFormat("%s_%s_H_", package, toc_ident)),
|
||||||
|
{{"/", "_"}});
|
||||||
|
absl::FPrintF(out_h.get(), kHFileHeaderFmt, package, toc_ident,
|
||||||
|
header_guard);
|
||||||
|
if (have_ns) {
|
||||||
|
absl::FPrintF(out_h.get(), kHNamespaceBeginFmt, ns);
|
||||||
|
}
|
||||||
|
absl::FPrintF(out_h.get(), kHFileTocDefsFmt, toc_ident);
|
||||||
|
if (have_ns) {
|
||||||
|
absl::FPrintF(out_h.get(), kHNamespaceEndFmt, ns);
|
||||||
|
}
|
||||||
|
absl::FPrintF(out_h.get(), kHFileFooterFmt, header_guard);
|
||||||
|
out_h.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write actual translation unit with the data.
|
||||||
|
File out_cc(*arg++, "wb");
|
||||||
|
--argc;
|
||||||
|
|
||||||
|
absl::FPrintF(out_cc.get(), kCcFileHeaderFmt, package, name);
|
||||||
|
if (have_ns) {
|
||||||
|
absl::FPrintF(out_cc.get(), kCcNamespaceBeginFmt, ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> toc_entries;
|
||||||
|
while (argc > 1) {
|
||||||
|
const char* in_filename = *arg++;
|
||||||
|
--argc;
|
||||||
|
File in(in_filename, "rb");
|
||||||
|
|
||||||
|
std::string basename = sandbox2::file_util::fileops::Basename(in_filename);
|
||||||
|
std::string ident = absl::StrCat(
|
||||||
|
"k", absl::StrReplaceAll(basename, {{".", "_"}, {"-", "_"}}));
|
||||||
|
absl::FPrintF(out_cc.get(), kCcDataBeginFmt, ident);
|
||||||
|
// Remember identifiers, they are needed in the kToc array.
|
||||||
|
toc_entries.emplace_back(std::move(basename), std::move(ident));
|
||||||
|
|
||||||
|
int c;
|
||||||
|
while ((c = fgetc(in.get())) != EOF) {
|
||||||
|
FWriteCEscapedC(c, out_cc.get());
|
||||||
|
}
|
||||||
|
in.Check();
|
||||||
|
|
||||||
|
absl::FPrintF(out_cc.get(), kCcDataEndFmt, ftell(in.get()));
|
||||||
|
}
|
||||||
|
absl::FPrintF(out_cc.get(), kCcFileTocDefsBegin);
|
||||||
|
for (const auto& entry : toc_entries) {
|
||||||
|
absl::FPrintF(out_cc.get(), kCcFileTocDefsEntryFmt, entry.first,
|
||||||
|
entry.second);
|
||||||
|
}
|
||||||
|
absl::FPrintF(out_cc.get(), kCcFileTocDefsEndFmt, toc_ident);
|
||||||
|
|
||||||
|
if (have_ns) {
|
||||||
|
absl::FPrintF(out_cc.get(), kCcNamespaceEndFmt, ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
out_cc.Check();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
53
sandboxed_api/bazel/filewrapper_test.cc
Normal file
53
sandboxed_api/bazel/filewrapper_test.cc
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "sandboxed_api/bazel/filewrapper_embedded.h"
|
||||||
|
#include "sandboxed_api/sandbox2/testing.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/file_helpers.h"
|
||||||
|
#include "sandboxed_api/util/status_matchers.h"
|
||||||
|
|
||||||
|
using ::sandbox2::GetTestSourcePath;
|
||||||
|
using ::sapi::IsOk;
|
||||||
|
using ::testing::Eq;
|
||||||
|
using ::testing::IsNull;
|
||||||
|
using ::testing::StrEq;
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(FilewrapperTest, BasicFunctionality) {
|
||||||
|
const FileToc* toc = filewrapper_embedded_create();
|
||||||
|
|
||||||
|
EXPECT_THAT(toc->name, StrEq("filewrapper_embedded.bin"));
|
||||||
|
EXPECT_THAT(toc->size, Eq(256));
|
||||||
|
|
||||||
|
std::string contents;
|
||||||
|
ASSERT_THAT(sandbox2::file::GetContents(
|
||||||
|
GetTestSourcePath("bazel/testdata/filewrapper_embedded.bin"),
|
||||||
|
&contents, sandbox2::file::Defaults()),
|
||||||
|
IsOk());
|
||||||
|
EXPECT_THAT(std::string(toc->data, toc->size), StrEq(contents));
|
||||||
|
|
||||||
|
++toc;
|
||||||
|
EXPECT_THAT(toc->name, IsNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace sapi
|
75
sandboxed_api/bazel/proto.bzl
Normal file
75
sandboxed_api/bazel/proto.bzl
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Generates proto targets in various languages."""
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@com_google_protobuf//:protobuf.bzl",
|
||||||
|
"cc_proto_library",
|
||||||
|
"py_proto_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
def sapi_proto_library(
|
||||||
|
name,
|
||||||
|
srcs = [],
|
||||||
|
deps = [],
|
||||||
|
alwayslink = False,
|
||||||
|
**kwargs):
|
||||||
|
"""Generates proto targets in various languages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name for proto_library and base for the cc_proto_library name, name +
|
||||||
|
"_cc".
|
||||||
|
srcs: Same as proto_library deps.
|
||||||
|
deps: Same as proto_library deps.
|
||||||
|
alwayslink: Same as cc_library.
|
||||||
|
**kwargs: proto_library arguments.
|
||||||
|
"""
|
||||||
|
if kwargs.get("has_services", False):
|
||||||
|
fail("Services are not currently supported.")
|
||||||
|
|
||||||
|
# TODO(cblichmann): For the time being, rely on the non-native rule
|
||||||
|
# provided by Protobuf. Once the Starlark C++ API has
|
||||||
|
# landed, it'll become possible to implement alwayslink
|
||||||
|
# natively.
|
||||||
|
cc_proto_library(
|
||||||
|
name = name,
|
||||||
|
srcs = srcs,
|
||||||
|
deps = deps,
|
||||||
|
alwayslink = alwayslink,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
native.cc_library(
|
||||||
|
name = name + "_cc",
|
||||||
|
deps = [":" + name],
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def sapi_py_proto_library(name, srcs = [], deps = [], **kwargs):
|
||||||
|
"""Generates proto targets for Python.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name for proto_library.
|
||||||
|
srcs: Same as py_proto_library deps.
|
||||||
|
deps: Ignored, provided for compatibility only.
|
||||||
|
**kwargs: proto_library arguments.
|
||||||
|
"""
|
||||||
|
_ignore = [deps]
|
||||||
|
py_proto_library(
|
||||||
|
name = name,
|
||||||
|
srcs = srcs,
|
||||||
|
default_runtime = "@com_google_protobuf//:protobuf_python",
|
||||||
|
protoc = "@com_google_protobuf//:protoc",
|
||||||
|
**kwargs
|
||||||
|
)
|
74
sandboxed_api/bazel/repositories.bzl
Normal file
74
sandboxed_api/bazel/repositories.bzl
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Repository rule that runs autotools' configure after extraction."""
|
||||||
|
|
||||||
|
def _configure(ctx):
|
||||||
|
bash_exe = ctx.os.environ["BAZEL_SH"] if "BAZEL_SH" in ctx.os.environ else "bash"
|
||||||
|
ctx.report_progress("Running configure script...")
|
||||||
|
|
||||||
|
# Run configure script and move host-specific directory to a well-known
|
||||||
|
# location.
|
||||||
|
ctx.execute(
|
||||||
|
[bash_exe, "-c", """
|
||||||
|
./configure --disable-dependency-tracking {args}
|
||||||
|
mv $(. config.guess) configure-bazel-gen || true
|
||||||
|
""".format(args = " ".join(ctx.attr.configure_args))],
|
||||||
|
quiet = ctx.attr.quiet,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _buildfile(ctx):
|
||||||
|
bash_exe = ctx.os.environ["BAZEL_SH"] if "BAZEL_SH" in ctx.os.environ else "bash"
|
||||||
|
if ctx.attr.build_file:
|
||||||
|
ctx.execute([bash_exe, "-c", "rm -f BUILD BUILD.bazel"])
|
||||||
|
ctx.symlink(ctx.attr.build_file, "BUILD.bazel")
|
||||||
|
elif ctx.attr.build_file_content:
|
||||||
|
ctx.execute([bash_exe, "-c", "rm -f BUILD.bazel"])
|
||||||
|
ctx.file("BUILD.bazel", ctx.attr.build_file_content)
|
||||||
|
|
||||||
|
def _autotools_repository_impl(ctx):
|
||||||
|
if ctx.attr.build_file and ctx.attr.build_file_content:
|
||||||
|
ctx.fail("Only one of build_file and build_file_content can be provided.")
|
||||||
|
ctx.download_and_extract(
|
||||||
|
ctx.attr.urls,
|
||||||
|
"", # output
|
||||||
|
ctx.attr.sha256,
|
||||||
|
"", # type
|
||||||
|
ctx.attr.strip_prefix,
|
||||||
|
)
|
||||||
|
_configure(ctx)
|
||||||
|
_buildfile(ctx)
|
||||||
|
|
||||||
|
autotools_repository = repository_rule(
|
||||||
|
attrs = {
|
||||||
|
"urls": attr.string_list(
|
||||||
|
mandatory = True,
|
||||||
|
allow_empty = False,
|
||||||
|
),
|
||||||
|
"sha256": attr.string(),
|
||||||
|
"strip_prefix": attr.string(),
|
||||||
|
"build_file": attr.label(
|
||||||
|
mandatory = True,
|
||||||
|
allow_single_file = [".BUILD"],
|
||||||
|
),
|
||||||
|
"build_file_content": attr.string(),
|
||||||
|
"configure_args": attr.string_list(
|
||||||
|
allow_empty = True,
|
||||||
|
),
|
||||||
|
"quiet": attr.bool(
|
||||||
|
default = True,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
implementation = _autotools_repository_impl,
|
||||||
|
)
|
264
sandboxed_api/bazel/sapi.bzl
Normal file
264
sandboxed_api/bazel/sapi.bzl
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Macros that simplify header and library generation for Sandboxed API."""
|
||||||
|
|
||||||
|
load("//sandboxed_api/bazel:embed_data.bzl", "sapi_cc_embed_data")
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
def append_arg(arguments, name, value):
|
||||||
|
if value:
|
||||||
|
arguments.append("{}".format(name))
|
||||||
|
arguments.append(value)
|
||||||
|
|
||||||
|
def append_all(arguments, name, values):
|
||||||
|
if values:
|
||||||
|
for v in values:
|
||||||
|
append_arg(arguments, name, v)
|
||||||
|
|
||||||
|
def get_embed_dir():
|
||||||
|
return native.package_name()
|
||||||
|
def sort_deps(deps):
|
||||||
|
colon_deps = [x for x in deps if x.startswith(":")]
|
||||||
|
other_deps = [x for x in deps if not x.startswith(":")]
|
||||||
|
return sorted(colon_deps) + sorted(other_deps)
|
||||||
|
|
||||||
|
def sapi_interface_impl(ctx):
|
||||||
|
"""Implementation of build rule that generates SAPI interface."""
|
||||||
|
|
||||||
|
# TODO(szwl): warn if input_files is not set and we didn't find anything
|
||||||
|
|
||||||
|
input_files_paths = []
|
||||||
|
input_files = []
|
||||||
|
|
||||||
|
args = []
|
||||||
|
append_arg(args, "--sapi_name", ctx.attr.lib_name)
|
||||||
|
append_arg(args, "--sapi_out", ctx.outputs.out.path)
|
||||||
|
append_arg(args, "--sapi_embed_dir", ctx.attr.embed_dir)
|
||||||
|
append_arg(args, "--sapi_embed_name", ctx.attr.embed_name)
|
||||||
|
append_arg(args, "--sapi_functions", ",".join(ctx.attr.functions))
|
||||||
|
append_arg(args, "--sapi_ns", ctx.attr.namespace)
|
||||||
|
if ctx.attr.isystem:
|
||||||
|
isystem = ctx.attr.isystem.files.to_list()[0]
|
||||||
|
append_arg(args, "--sapi_isystem", isystem.path)
|
||||||
|
input_files += [isystem]
|
||||||
|
|
||||||
|
# Parse provided files.
|
||||||
|
|
||||||
|
# The parser doesn't need the entire set of transitive headers
|
||||||
|
# here, just the top-level cc_library headers. It would be nice
|
||||||
|
# if Skylark or Bazel provided this, but it is surprisingly hard
|
||||||
|
# to get.
|
||||||
|
#
|
||||||
|
# Allow all headers through that contain the dependency's
|
||||||
|
# package path. Including extra headers is harmless except that
|
||||||
|
# we may hit the Bazel file-count limit, so be conservative and
|
||||||
|
# pass a lot through that we don't strictly need.
|
||||||
|
#
|
||||||
|
|
||||||
|
extra_flags = []
|
||||||
|
if "cc" in dir(ctx.attr.lib) and ctx.attr.lib.cc:
|
||||||
|
# Append system headers as dependencies
|
||||||
|
input_files += ctx.attr.lib[CcInfo].compilation_context.headers.to_list()
|
||||||
|
|
||||||
|
# ctx.attr.lib[CcInfo].compilation_context.system_includes seems
|
||||||
|
# to be equal to .system_include_directories
|
||||||
|
append_all(extra_flags, "-D", ctx.attr.lib.cc.defines)
|
||||||
|
append_all(extra_flags, "-isystem", ctx.attr.lib.cc.system_include_directories)
|
||||||
|
append_all(extra_flags, "-iquote", ctx.attr.lib.cc.quote_include_directories)
|
||||||
|
|
||||||
|
for h in ctx.attr.lib.cc.transitive_headers:
|
||||||
|
# Collect all headers as dependency in case libclang needs them.
|
||||||
|
if h.extension == "h" and "/PROTECTED/" not in h.path:
|
||||||
|
input_files.append(h)
|
||||||
|
if ctx.attr.input_files:
|
||||||
|
for target in ctx.attr.input_files:
|
||||||
|
if target.files:
|
||||||
|
for f in target.files:
|
||||||
|
input_files_paths.append(f.path)
|
||||||
|
input_files.append(f)
|
||||||
|
|
||||||
|
# Try to find files automatically.
|
||||||
|
else:
|
||||||
|
for h in ctx.attr.lib.cc.transitive_headers:
|
||||||
|
# Collect all headers as dependency in case clang needs them.
|
||||||
|
if h.extension == "h" and "/PROTECTED/" not in h.path:
|
||||||
|
input_files.append(h)
|
||||||
|
|
||||||
|
# Include only headers coming from the target
|
||||||
|
# not ones that it depends on by comparing the label packages.
|
||||||
|
if (h.owner.package == ctx.attr.lib.label.package):
|
||||||
|
input_files_paths.append(h.path)
|
||||||
|
|
||||||
|
append_arg(args, "--sapi_in", ",".join(input_files_paths))
|
||||||
|
args += ["--"] + extra_flags
|
||||||
|
else:
|
||||||
|
# TODO(szwl): Error out if the lib has no cc.
|
||||||
|
pass
|
||||||
|
|
||||||
|
progress_msg = ("Generating {} from {} header files." +
|
||||||
|
"").format(ctx.outputs.out.short_path, len(input_files_paths))
|
||||||
|
ctx.actions.run(
|
||||||
|
inputs = input_files,
|
||||||
|
outputs = [ctx.outputs.out],
|
||||||
|
arguments = args,
|
||||||
|
progress_message = progress_msg,
|
||||||
|
executable = ctx.executable._sapi_generator,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build rule that generates SAPI interface.
|
||||||
|
sapi_interface = rule(
|
||||||
|
implementation = sapi_interface_impl,
|
||||||
|
attrs = {
|
||||||
|
"out": attr.output(mandatory = True),
|
||||||
|
"embed_dir": attr.string(),
|
||||||
|
"embed_name": attr.string(),
|
||||||
|
"functions": attr.string_list(allow_empty = True, default = []),
|
||||||
|
"include_prefix": attr.string(),
|
||||||
|
"input_files": attr.label_list(allow_files = True),
|
||||||
|
"lib": attr.label(mandatory = True),
|
||||||
|
"lib_name": attr.string(mandatory = True),
|
||||||
|
"namespace": attr.string(),
|
||||||
|
"isystem": attr.label(),
|
||||||
|
"_sapi_generator": attr.label(
|
||||||
|
executable = True,
|
||||||
|
cfg = "host",
|
||||||
|
allow_files = True,
|
||||||
|
default = Label("//sandboxed_api/" +
|
||||||
|
"tools/generator2:sapi_generator"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
output_to_genfiles = True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def sapi_library(
|
||||||
|
name,
|
||||||
|
lib,
|
||||||
|
lib_name,
|
||||||
|
namespace = "",
|
||||||
|
embed = False,
|
||||||
|
add_default_deps = True,
|
||||||
|
srcs = [],
|
||||||
|
hdrs = [],
|
||||||
|
functions = [],
|
||||||
|
header = "",
|
||||||
|
input_files = [],
|
||||||
|
deps = [],
|
||||||
|
tags = [],
|
||||||
|
visibility = None):
|
||||||
|
"""BUILD rule providing implementation of a Sandboxed API library."""
|
||||||
|
|
||||||
|
common = {
|
||||||
|
"tags": tags,
|
||||||
|
}
|
||||||
|
if visibility:
|
||||||
|
common["visibility"] = visibility
|
||||||
|
|
||||||
|
generated_header = name + ".sapi.h"
|
||||||
|
|
||||||
|
# Reference (pull into the archive) required functions only. If the functions'
|
||||||
|
# array is empty, pull in the whole archive (may not compile with MSAN
|
||||||
|
# )
|
||||||
|
exported_funcs = ["-Wl,--export-dynamic-symbol," + s for s in functions]
|
||||||
|
if (not exported_funcs):
|
||||||
|
exported_funcs = [
|
||||||
|
"-Wl,--whole-archive",
|
||||||
|
"-Wl,--allow-multiple-definition",
|
||||||
|
]
|
||||||
|
|
||||||
|
lib_hdrs = hdrs or []
|
||||||
|
if header:
|
||||||
|
lib_hdrs += [header]
|
||||||
|
else:
|
||||||
|
lib_hdrs += [generated_header]
|
||||||
|
|
||||||
|
default_deps = [
|
||||||
|
"//sandboxed_api/sandbox2",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Library that contains generated interface and sandboxed binary as a data
|
||||||
|
# dependency. Add this as a dependency instead of original library.
|
||||||
|
native.cc_library(
|
||||||
|
name = name,
|
||||||
|
srcs = srcs,
|
||||||
|
hdrs = lib_hdrs,
|
||||||
|
data = [":" + name + ".bin"],
|
||||||
|
deps = sort_deps(
|
||||||
|
[
|
||||||
|
"//sandboxed_api:sapi",
|
||||||
|
"//sandboxed_api:vars",
|
||||||
|
] + deps +
|
||||||
|
([":" + name + "_embed"] if embed else []) +
|
||||||
|
(default_deps if add_default_deps else []),
|
||||||
|
),
|
||||||
|
**common
|
||||||
|
)
|
||||||
|
|
||||||
|
native.cc_binary(
|
||||||
|
name = name + ".bin",
|
||||||
|
linkopts = [
|
||||||
|
# The sandboxing client must have access to all
|
||||||
|
"-Wl,-E", # symbols used in the sandboxed library, so these
|
||||||
|
] + exported_funcs, # must be both referenced, and exported
|
||||||
|
deps = [
|
||||||
|
":" + name + ".lib",
|
||||||
|
"//sandboxed_api:client",
|
||||||
|
],
|
||||||
|
**common
|
||||||
|
)
|
||||||
|
|
||||||
|
native.cc_library(
|
||||||
|
name = name + ".lib",
|
||||||
|
deps = [
|
||||||
|
lib,
|
||||||
|
],
|
||||||
|
alwayslink = 1, # All functions are linked into depending binaries
|
||||||
|
**common
|
||||||
|
)
|
||||||
|
|
||||||
|
embed_name = ""
|
||||||
|
embed_dir = ""
|
||||||
|
if embed:
|
||||||
|
embed_name = name
|
||||||
|
|
||||||
|
sapi_cc_embed_data(
|
||||||
|
srcs = [name + ".bin"],
|
||||||
|
name = name + "_embed",
|
||||||
|
namespace = namespace,
|
||||||
|
**common
|
||||||
|
)
|
||||||
|
embed_dir = get_embed_dir()
|
||||||
|
|
||||||
|
sapi_interface(
|
||||||
|
name = name + ".interface",
|
||||||
|
lib_name = lib_name,
|
||||||
|
lib = lib,
|
||||||
|
functions = functions,
|
||||||
|
input_files = input_files,
|
||||||
|
out = generated_header,
|
||||||
|
embed_name = embed_name,
|
||||||
|
embed_dir = embed_dir,
|
||||||
|
namespace = namespace,
|
||||||
|
isystem = ":" + name + ".isystem",
|
||||||
|
**common
|
||||||
|
)
|
||||||
|
|
||||||
|
native.genrule(
|
||||||
|
name = name + ".isystem",
|
||||||
|
outs = [name + ".isystem.list"],
|
||||||
|
cmd = """echo |
|
||||||
|
$(CC) -E -x c++ - -v 2>&1 |
|
||||||
|
awk '/> search starts here:/{flag=1;next}/End of search/{flag=0}flag' > $@
|
||||||
|
""",
|
||||||
|
)
|
BIN
sandboxed_api/bazel/testdata/filewrapper_embedded.bin
vendored
Normal file
BIN
sandboxed_api/bazel/testdata/filewrapper_embedded.bin
vendored
Normal file
Binary file not shown.
93
sandboxed_api/call.h
Normal file
93
sandboxed_api/call.h
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_CALL_H_
|
||||||
|
#define SANDBOXED_API_CALL_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "sandboxed_api/var_type.h"
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
namespace comms {
|
||||||
|
|
||||||
|
struct ReallocRequest {
|
||||||
|
uint64_t old_addr;
|
||||||
|
uint64_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Types of TAGs used with Comms channel.
|
||||||
|
// TODO(cblichmann): Mark these as "inline" once we're on C++17.
|
||||||
|
// Call:
|
||||||
|
constexpr uint32_t kMsgCall = 0x101;
|
||||||
|
constexpr uint32_t kMsgAllocate = 0x102;
|
||||||
|
constexpr uint32_t kMsgFree = 0x103;
|
||||||
|
constexpr uint32_t kMsgExit = 0x104;
|
||||||
|
constexpr uint32_t kMsgSymbol = 0x105;
|
||||||
|
constexpr uint32_t kMsgSendFd = 0x106;
|
||||||
|
constexpr uint32_t kMsgRecvFd = 0x107;
|
||||||
|
constexpr uint32_t kMsgClose = 0x108;
|
||||||
|
constexpr uint32_t kMsgReallocate = 0x109;
|
||||||
|
// Return:
|
||||||
|
constexpr uint32_t kMsgReturn = 0x201;
|
||||||
|
|
||||||
|
} // namespace comms
|
||||||
|
|
||||||
|
struct FuncCall {
|
||||||
|
// Used with HandleCallMsg:
|
||||||
|
enum {
|
||||||
|
kFuncNameMax = 128,
|
||||||
|
kArgsMax = 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to be called.
|
||||||
|
char func[kFuncNameMax];
|
||||||
|
// Return type.
|
||||||
|
v::Type ret_type;
|
||||||
|
// Size of the return value (in bytes).
|
||||||
|
size_t ret_size;
|
||||||
|
// Number of input arguments.
|
||||||
|
size_t argc;
|
||||||
|
// Types of the input arguments.
|
||||||
|
v::Type arg_type[kArgsMax];
|
||||||
|
// Size (in bytes) of input arguments.
|
||||||
|
size_t arg_size[kArgsMax];
|
||||||
|
// Arguments to the call.
|
||||||
|
union {
|
||||||
|
uintptr_t arg_int;
|
||||||
|
long double arg_float;
|
||||||
|
} args[kArgsMax];
|
||||||
|
// Auxiliary type:
|
||||||
|
// For pointers: type of the data it points to,
|
||||||
|
// For others: unspecified.
|
||||||
|
v::Type aux_type[kArgsMax];
|
||||||
|
// Size of the auxiliary data (e.g. a structure the pointer points to).
|
||||||
|
size_t aux_size[kArgsMax];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FuncRet {
|
||||||
|
// Return type:
|
||||||
|
v::Type ret_type;
|
||||||
|
// Return value.
|
||||||
|
union {
|
||||||
|
uintptr_t int_val;
|
||||||
|
long double float_val;
|
||||||
|
};
|
||||||
|
// Status of the operation: success/failure.
|
||||||
|
bool success;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sapi
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_CALL_H_
|
451
sandboxed_api/client.cc
Normal file
451
sandboxed_api/client.cc
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <iterator>
|
||||||
|
#include <list>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "google/protobuf/descriptor.h"
|
||||||
|
#include "google/protobuf/message.h"
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "sandboxed_api/call.h"
|
||||||
|
#include "sandboxed_api/lenval_core.h"
|
||||||
|
#include "sandboxed_api/proto_arg.pb.h"
|
||||||
|
#include "sandboxed_api/sandbox2/client.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkingclient.h"
|
||||||
|
#include "sandboxed_api/vars.h"
|
||||||
|
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
#include <sanitizer/allocator_interface.h>
|
||||||
|
#endif
|
||||||
|
#include <ffi.h>
|
||||||
|
#include <ffitarget.h>
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Guess the FFI type on the basis of data size and float/non-float/bool.
|
||||||
|
static ffi_type* GetFFIType(size_t size, v::Type type) {
|
||||||
|
switch (type) {
|
||||||
|
case v::Type::kVoid:
|
||||||
|
return &ffi_type_void;
|
||||||
|
case v::Type::kPointer:
|
||||||
|
return &ffi_type_pointer;
|
||||||
|
case v::Type::kFd:
|
||||||
|
return &ffi_type_sint;
|
||||||
|
case v::Type::kFloat:
|
||||||
|
switch (size) {
|
||||||
|
case sizeof(float):
|
||||||
|
return &ffi_type_float;
|
||||||
|
case sizeof(double):
|
||||||
|
return &ffi_type_double;
|
||||||
|
case sizeof(long double):
|
||||||
|
return &ffi_type_longdouble;
|
||||||
|
default:
|
||||||
|
LOG(FATAL) << "Unknown floating-point size: " << size;
|
||||||
|
}
|
||||||
|
case v::Type::kInt:
|
||||||
|
switch (size) {
|
||||||
|
case 1:
|
||||||
|
return &ffi_type_uint8;
|
||||||
|
case 2:
|
||||||
|
return &ffi_type_uint16;
|
||||||
|
case 4:
|
||||||
|
return &ffi_type_uint32;
|
||||||
|
case 8:
|
||||||
|
return &ffi_type_uint64;
|
||||||
|
default:
|
||||||
|
LOG(FATAL) << "Unknown integral size: " << size;
|
||||||
|
}
|
||||||
|
case v::Type::kStruct:
|
||||||
|
LOG(FATAL) << "Structs are not supported as function arguments";
|
||||||
|
case v::Type::kProto:
|
||||||
|
LOG(FATAL) << "Protos are not supported as function arguments";
|
||||||
|
default:
|
||||||
|
LOG(FATAL) << "Unknown type: " << type << " of size: " << size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provides an interface to prepare the arguments for a function call.
|
||||||
|
// In case of protobuf arguments, the class allocates and manages
|
||||||
|
// memory for the deserialized protobuf.
|
||||||
|
class FunctionCallPreparer {
|
||||||
|
public:
|
||||||
|
explicit FunctionCallPreparer(const FuncCall& call) {
|
||||||
|
CHECK(call.argc <= FuncCall::kArgsMax)
|
||||||
|
<< "Number of arguments of a sandbox call exceeds limits.";
|
||||||
|
for (size_t i = 0; i < call.argc; i++) {
|
||||||
|
arg_types_[i] = GetFFIType(call.arg_size[i], call.arg_type[i]);
|
||||||
|
}
|
||||||
|
ret_type_ = GetFFIType(call.ret_size, call.ret_type);
|
||||||
|
for (size_t i = 0; i < call.argc; i++) {
|
||||||
|
if (call.arg_type[i] == v::Type::kPointer &&
|
||||||
|
call.aux_type[i] == v::Type::kProto) {
|
||||||
|
// Deserialize protobuf stored in the LenValueStruct and keep a
|
||||||
|
// reference to both. This way we are able to update the content of the
|
||||||
|
// LenValueStruct (when the sandboxee modifies the protobuf).
|
||||||
|
// This will also make sure that the protobuf is freed afterwards.
|
||||||
|
arg_values_[i] = GetDeserializedProto(
|
||||||
|
reinterpret_cast<LenValStruct*>(call.args[i].arg_int));
|
||||||
|
} else {
|
||||||
|
if (call.arg_type[i] == v::Type::kFloat) {
|
||||||
|
arg_values_[i] =
|
||||||
|
reinterpret_cast<const void*>(&call.args[i].arg_float);
|
||||||
|
} else {
|
||||||
|
arg_values_[i] = reinterpret_cast<const void*>(&call.args[i].arg_int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~FunctionCallPreparer() {
|
||||||
|
for (const auto& idx_proto : protos_to_be_destroyed_) {
|
||||||
|
const auto proto = idx_proto.second;
|
||||||
|
LenValStruct* lvs = idx_proto.first;
|
||||||
|
// There is no way to figure out whether the protobuf structure has
|
||||||
|
// changed or not, so we always serialize the protobuf again and replace
|
||||||
|
// the LenValStruct content.
|
||||||
|
std::vector<uint8_t> serialized = SerializeProto(*proto);
|
||||||
|
// Reallocate the LV memory to match it's length.
|
||||||
|
if (lvs->size != serialized.size()) {
|
||||||
|
void* newdata = realloc(lvs->data, serialized.size());
|
||||||
|
if (!newdata) {
|
||||||
|
LOG(FATAL) << "Failed to reallocate protobuf buffer (size="
|
||||||
|
<< serialized.size() << ")";
|
||||||
|
}
|
||||||
|
lvs->size = serialized.size();
|
||||||
|
lvs->data = newdata;
|
||||||
|
}
|
||||||
|
memcpy(lvs->data, serialized.data(), serialized.size());
|
||||||
|
|
||||||
|
delete proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ffi_type* ret_type() const { return ret_type_; }
|
||||||
|
ffi_type** arg_types() const { return const_cast<ffi_type**>(arg_types_); }
|
||||||
|
void** arg_values() const { return const_cast<void**>(arg_values_); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Deserializes the protobuf argument.
|
||||||
|
google::protobuf::Message** GetDeserializedProto(LenValStruct* src) {
|
||||||
|
ProtoArg proto_arg;
|
||||||
|
if (!proto_arg.ParseFromArray(src->data, src->size)) {
|
||||||
|
LOG(FATAL) << "Unable to parse ProtoArg.";
|
||||||
|
}
|
||||||
|
const google::protobuf::Descriptor* desc =
|
||||||
|
google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(
|
||||||
|
proto_arg.full_name());
|
||||||
|
LOG_IF(FATAL, desc == nullptr) << "Unable to find the descriptor for '"
|
||||||
|
<< proto_arg.full_name() << "'" << desc;
|
||||||
|
google::protobuf::Message* deserialized_proto =
|
||||||
|
google::protobuf::MessageFactory::generated_factory()->GetPrototype(desc)->New();
|
||||||
|
LOG_IF(FATAL, deserialized_proto == nullptr)
|
||||||
|
<< "Unable to create deserialized proto for " << proto_arg.full_name();
|
||||||
|
if (!deserialized_proto->ParseFromString(proto_arg.protobuf_data())) {
|
||||||
|
LOG(FATAL) << "Unable to deserialized proto for "
|
||||||
|
<< proto_arg.full_name();
|
||||||
|
}
|
||||||
|
protos_to_be_destroyed_.push_back({src, deserialized_proto});
|
||||||
|
return &protos_to_be_destroyed_.back().second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use list instead of vector to preserve references even with modifications.
|
||||||
|
// Contains pairs of lenval message pointer -> deserialized message
|
||||||
|
// so that we can serialize the argument again after the function call.
|
||||||
|
std::list<std::pair<LenValStruct*, google::protobuf::Message*>> protos_to_be_destroyed_;
|
||||||
|
ffi_type* ret_type_;
|
||||||
|
ffi_type* arg_types_[FuncCall::kArgsMax];
|
||||||
|
const void* arg_values_[FuncCall::kArgsMax];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace client {
|
||||||
|
|
||||||
|
// Error codes in the client code:
|
||||||
|
enum class Error : uintptr_t {
|
||||||
|
kUnset = 0,
|
||||||
|
kDlOpen,
|
||||||
|
kDlSym,
|
||||||
|
kCall,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles requests to make function calls.
|
||||||
|
void HandleCallMsg(const FuncCall& call, FuncRet* ret) {
|
||||||
|
VLOG(1) << "HandleMsgCall, func: '" << call.func
|
||||||
|
<< "', # of args: " << call.argc;
|
||||||
|
|
||||||
|
ret->ret_type = call.ret_type;
|
||||||
|
|
||||||
|
void* handle = dlopen(nullptr, RTLD_NOW);
|
||||||
|
if (handle == nullptr) {
|
||||||
|
LOG(ERROR) << "dlopen(nullptr, RTLD_NOW)";
|
||||||
|
ret->success = false;
|
||||||
|
ret->int_val = static_cast<uintptr_t>(Error::kDlOpen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto f = dlsym(handle, call.func);
|
||||||
|
if (f == nullptr) {
|
||||||
|
LOG(ERROR) << "Function '" << call.func << "' not found";
|
||||||
|
ret->success = false;
|
||||||
|
ret->int_val = static_cast<uintptr_t>(Error::kDlSym);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FunctionCallPreparer arg_prep(call);
|
||||||
|
ffi_cif cif;
|
||||||
|
if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, call.argc, arg_prep.ret_type(),
|
||||||
|
arg_prep.arg_types()) != FFI_OK) {
|
||||||
|
ret->success = false;
|
||||||
|
ret->int_val = static_cast<uintptr_t>(Error::kCall);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret->ret_type == v::Type::kFloat) {
|
||||||
|
ffi_call(&cif, FFI_FN(f), &ret->float_val, arg_prep.arg_values());
|
||||||
|
} else {
|
||||||
|
ffi_call(&cif, FFI_FN(f), &ret->int_val, arg_prep.arg_values());
|
||||||
|
}
|
||||||
|
|
||||||
|
ret->success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles requests to allocate memory inside the sandboxee.
|
||||||
|
void HandleAllocMsg(const uintptr_t size, FuncRet* ret) {
|
||||||
|
VLOG(1) << "HandleAllocMsg: size=" << size;
|
||||||
|
|
||||||
|
ret->ret_type = v::Type::kPointer;
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
// Memory is copied to the pointer using an API that the memory sanitizer
|
||||||
|
// is blind to (process_vm_writev). Initialize the memory here so that
|
||||||
|
// the sandboxed code can still be tested with the memory sanitizer.
|
||||||
|
ret->int_val =
|
||||||
|
reinterpret_cast<uintptr_t>(calloc(1, static_cast<size_t>(size)));
|
||||||
|
#else
|
||||||
|
ret->int_val = reinterpret_cast<uintptr_t>(malloc(static_cast<size_t>(size)));
|
||||||
|
#endif
|
||||||
|
ret->success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like HandleAllocMsg(), but handles requests to reallocate memory.
|
||||||
|
void HandleReallocMsg(uintptr_t ptr, uintptr_t size, FuncRet* ret) {
|
||||||
|
VLOG(1) << "HandleReallocMsg(" << absl::StrCat(absl::Hex(ptr)) << ", " << size
|
||||||
|
<< ")";
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
const size_t orig_size =
|
||||||
|
__sanitizer_get_allocated_size(reinterpret_cast<const void*>(ptr));
|
||||||
|
#endif
|
||||||
|
ret->ret_type = v::Type::kPointer;
|
||||||
|
ret->int_val = reinterpret_cast<uintptr_t>(
|
||||||
|
realloc(const_cast<void*>(reinterpret_cast<const void*>(ptr)), size));
|
||||||
|
ret->success = true;
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
// Memory is copied to the pointer using an API that the memory sanitizer
|
||||||
|
// is blind to (process_vm_writev). Initialize the memory here so that
|
||||||
|
// the sandboxed code can still be tested with the memory sanitizer.
|
||||||
|
if (orig_size < size && ret->int_val != 0) {
|
||||||
|
memset(reinterpret_cast<void*>(ret->int_val + orig_size), 0,
|
||||||
|
size - orig_size);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles requests to free memory previously allocated by HandleAllocMsg() and
|
||||||
|
// HandleReallocMsg().
|
||||||
|
void HandleFreeMsg(uintptr_t ptr, FuncRet* ret) {
|
||||||
|
VLOG(1) << "HandleFreeMsg: free(0x" << absl::StrCat(absl::Hex(ptr)) << ")";
|
||||||
|
|
||||||
|
free(const_cast<void*>(reinterpret_cast<const void*>(ptr)));
|
||||||
|
ret->ret_type = v::Type::kVoid;
|
||||||
|
ret->success = true;
|
||||||
|
ret->int_val = 0ULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles requests to find a symbol value.
|
||||||
|
void HandleSymbolMsg(const char* symname, FuncRet* ret) {
|
||||||
|
ret->ret_type = v::Type::kPointer;
|
||||||
|
|
||||||
|
void* handle = dlopen(nullptr, RTLD_NOW);
|
||||||
|
if (handle == nullptr) {
|
||||||
|
ret->success = false;
|
||||||
|
ret->int_val = static_cast<uintptr_t>(Error::kDlOpen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret->int_val = reinterpret_cast<uintptr_t>(dlsym(handle, symname));
|
||||||
|
ret->success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles requests to receive a file descriptor from sandboxer.
|
||||||
|
void HandleSendFd(sandbox2::Comms* comms, FuncRet* ret) {
|
||||||
|
ret->ret_type = v::Type::kInt;
|
||||||
|
int fd = -1;
|
||||||
|
|
||||||
|
if (comms->RecvFD(&fd) == false) {
|
||||||
|
ret->success = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret->int_val = fd;
|
||||||
|
ret->success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles requests to send a file descriptor back to sandboxer.
|
||||||
|
void HandleRecvFd(sandbox2::Comms* comms, int fd_to_transfer, FuncRet* ret) {
|
||||||
|
ret->ret_type = v::Type::kVoid;
|
||||||
|
|
||||||
|
if (comms->SendFD(fd_to_transfer) == false) {
|
||||||
|
ret->success = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret->success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles requests to close a file descriptor in the sandboxee.
|
||||||
|
void HandleCloseFd(sandbox2::Comms* comms, int fd_to_close, FuncRet* ret) {
|
||||||
|
VLOG(1) << "HandleCloseFd: close(" << fd_to_close << ")";
|
||||||
|
close(fd_to_close);
|
||||||
|
|
||||||
|
ret->ret_type = v::Type::kVoid;
|
||||||
|
ret->success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static T BytesAs(const std::vector<uint8_t>& bytes) {
|
||||||
|
static_assert(std::is_trivial<T>(),
|
||||||
|
"only trivial types can be used with BytesAs");
|
||||||
|
CHECK_EQ(bytes.size(), sizeof(T));
|
||||||
|
T rv;
|
||||||
|
memcpy(&rv, bytes.data(), sizeof(T));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServeRequest(sandbox2::Comms* comms) {
|
||||||
|
uint32_t tag;
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
|
||||||
|
CHECK(comms->RecvTLV(&tag, &bytes));
|
||||||
|
|
||||||
|
FuncRet ret{};
|
||||||
|
ret.ret_type = v::Type::kVoid;
|
||||||
|
ret.int_val = static_cast<uintptr_t>(Error::kUnset);
|
||||||
|
ret.success = false;
|
||||||
|
|
||||||
|
switch (tag) {
|
||||||
|
case comms::kMsgCall:
|
||||||
|
VLOG(1) << "Client::kMsgCall";
|
||||||
|
HandleCallMsg(BytesAs<FuncCall>(bytes), &ret);
|
||||||
|
break;
|
||||||
|
case comms::kMsgAllocate:
|
||||||
|
VLOG(1) << "Client::kMsgAllocate";
|
||||||
|
HandleAllocMsg(BytesAs<uintptr_t>(bytes), &ret);
|
||||||
|
break;
|
||||||
|
case comms::kMsgReallocate:
|
||||||
|
VLOG(1) << "Client::kMsgReallocate";
|
||||||
|
{
|
||||||
|
auto req = BytesAs<comms::ReallocRequest>(bytes);
|
||||||
|
HandleReallocMsg(static_cast<uintptr_t>(req.old_addr),
|
||||||
|
static_cast<uintptr_t>(req.size), &ret);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case comms::kMsgFree:
|
||||||
|
VLOG(1) << "Client::kMsgFree";
|
||||||
|
HandleFreeMsg(BytesAs<uintptr_t>(bytes), &ret);
|
||||||
|
break;
|
||||||
|
case comms::kMsgSymbol:
|
||||||
|
CHECK_EQ(bytes.size(),
|
||||||
|
1 + std::distance(bytes.begin(),
|
||||||
|
std::find(bytes.begin(), bytes.end(), '\0')));
|
||||||
|
VLOG(1) << "Received Client::kMsgSymbol message";
|
||||||
|
HandleSymbolMsg(reinterpret_cast<const char*>(bytes.data()), &ret);
|
||||||
|
break;
|
||||||
|
case comms::kMsgExit:
|
||||||
|
VLOG(1) << "Received Client::kMsgExit message";
|
||||||
|
syscall(__NR_exit_group, 0UL);
|
||||||
|
break;
|
||||||
|
case comms::kMsgSendFd:
|
||||||
|
VLOG(1) << "Received Client::kMsgSendFd message";
|
||||||
|
HandleSendFd(comms, &ret);
|
||||||
|
break;
|
||||||
|
case comms::kMsgRecvFd:
|
||||||
|
VLOG(1) << "Received Client::kMsgRecvFd message";
|
||||||
|
HandleRecvFd(comms, BytesAs<int>(bytes), &ret);
|
||||||
|
break;
|
||||||
|
case comms::kMsgClose:
|
||||||
|
VLOG(1) << "Received Client::kMsgClose message";
|
||||||
|
HandleCloseFd(comms, BytesAs<int>(bytes), &ret);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG(FATAL) << "Received unknown tag: " << tag;
|
||||||
|
break; // Not reached
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret.ret_type == v::Type::kFloat) {
|
||||||
|
VLOG(1) << "Returned value: " << ret.float_val
|
||||||
|
<< ", Success: " << (ret.success ? "Yes" : "No");
|
||||||
|
} else {
|
||||||
|
VLOG(1) << "Returned value: " << ret.int_val << " (0x"
|
||||||
|
<< absl::StrCat(absl::Hex(ret.int_val))
|
||||||
|
<< "), Success: " << (ret.success ? "Yes" : "No");
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(comms->SendTLV(comms::kMsgReturn, sizeof(ret),
|
||||||
|
reinterpret_cast<uint8_t*>(&ret)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace client
|
||||||
|
} // namespace sapi
|
||||||
|
|
||||||
|
extern "C" ABSL_ATTRIBUTE_WEAK int main(int argc, char** argv) {
|
||||||
|
google::SetCommandLineOptionWithMode("userspace_coredumper", "false",
|
||||||
|
google::SET_FLAG_IF_DEFAULT);
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
// Note regarding the FD usage here: Parent and child seem to make use of the
|
||||||
|
// same FD, although this is not true. During process setup `dup2()` will be
|
||||||
|
// called to replace the FD `kSandbox2ClientCommsFD`.
|
||||||
|
// We do not use a new comms object here as the destructor would close our FD.
|
||||||
|
sandbox2::Comms comms{sandbox2::Comms::kSandbox2ClientCommsFD};
|
||||||
|
sandbox2::ForkingClient s2client{&comms};
|
||||||
|
|
||||||
|
// Forkserver loop.
|
||||||
|
while (true) {
|
||||||
|
pid_t pid = s2client.WaitAndFork();
|
||||||
|
if (pid == -1) {
|
||||||
|
LOG(FATAL) << "Could not spawn a new sandboxee";
|
||||||
|
}
|
||||||
|
if (pid == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child thread.
|
||||||
|
s2client.SandboxMeHere();
|
||||||
|
|
||||||
|
// Run SAPI stub.
|
||||||
|
while (true) {
|
||||||
|
sapi::client::ServeRequest(&comms);
|
||||||
|
}
|
||||||
|
LOG(FATAL) << "Unreachable";
|
||||||
|
}
|
37
sandboxed_api/docs/examples.md
Normal file
37
sandboxed_api/docs/examples.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
We have prepared some examples, which might help you to implement your first
|
||||||
|
Sandboxed API library.
|
||||||
|
|
||||||
|
|
||||||
|
## Sum
|
||||||
|
|
||||||
|
A demo library implementing a few [C functions](../examples/sum/lib/sum.c) and a
|
||||||
|
single [C++ function](../examples/sum/lib/sum_cpp.cc).
|
||||||
|
It uses ProtoBuffs to exchange data between host code and the SAPI Library.
|
||||||
|
|
||||||
|
* The sandbox definition can be found in the
|
||||||
|
[sandbox.h](../examples/sum/lib/sandbox.h) file.
|
||||||
|
* The (automatically generated) function annotation file (a file providing
|
||||||
|
prototypes of sandboxed functions) can be found in
|
||||||
|
`bazel-out/genfiles/sandboxed_api/examples/sum/lib/sum-sapi.sapi.h`
|
||||||
|
after a Bazel build.
|
||||||
|
* The actual execution logic (a.k.a. host code) making use of the exported
|
||||||
|
sandboxed procedures can be found in [main_sum.cc](../examples/sum/main_sum.cc).
|
||||||
|
|
||||||
|
|
||||||
|
## zlib
|
||||||
|
|
||||||
|
This is a demo implementation (functional, but currently not used in production)
|
||||||
|
for the zlib library exporting some of its functions, and making them available
|
||||||
|
to the [host code](../examples/zlib/main_zlib.cc).
|
||||||
|
|
||||||
|
The demonstrated functionality of the host code is decoding of zlib streams
|
||||||
|
from stdin to stdout.
|
||||||
|
|
||||||
|
This SAPI library doesn't use the `sandbox.h` file, as it uses the default
|
||||||
|
Sandbox2 policy, and an embedded SAPI library, so there is no need to provide
|
||||||
|
`sapi::Sandbox::GetLibPath()` nor `sapi::Sandbox::GetPolicy()` methods.
|
||||||
|
|
||||||
|
The zlib SAPI can be found in [//sapi_sandbox/examples/zlib](../examples/zlib),
|
||||||
|
along with its [host code](../examples/zlib/main_zlib.cc).
|
71
sandboxed_api/docs/getting-started.md
Normal file
71
sandboxed_api/docs/getting-started.md
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# Getting started with SAPI
|
||||||
|
|
||||||
|
## Build Dependencies
|
||||||
|
|
||||||
|
To build and run code with SAPI, the following dependencies must be installed
|
||||||
|
on the system:
|
||||||
|
|
||||||
|
* To compile your code: GCC 6 (version 7 or higher preferred) or Clang 7 (or
|
||||||
|
higher)
|
||||||
|
* For auto-generating header files: Clang Python Bindings
|
||||||
|
* [Bazel](https://bazel.build/) version 0.23.0
|
||||||
|
* Python 2.7 with type annotations
|
||||||
|
* Linux userspace API headers
|
||||||
|
|
||||||
|
On a system running Debian 10 "Buster", these commands will install the
|
||||||
|
necessary packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "deb http://storage.googleapis.com/bazel-apt stable jdk1.8" | \
|
||||||
|
sudo tee /etc/apt/sources.list.d/bazel.list
|
||||||
|
wget -qO - https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
|
||||||
|
sudo apt-get install -qy python-typing python-clang-7 libclang-7-dev
|
||||||
|
sudo apt-get install -qy build-essential linux-libc-dev bazel
|
||||||
|
```
|
||||||
|
|
||||||
|
Please refer to the
|
||||||
|
[Bazel documentation](https://docs.bazel.build/versions/master/bazel-overview.html)
|
||||||
|
for information on how to change the default compiler toolchain.
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Under [Examples](examples.md) you can find a few libraries, previously prepared
|
||||||
|
by the SAPI team.
|
||||||
|
|
||||||
|
|
||||||
|
## Development Process
|
||||||
|
|
||||||
|
You will have to prepare two parts of your a sandbox library project. The
|
||||||
|
sandboxed library part (**SAPI library**), and the **host code**
|
||||||
|
which will make use of functionality exposed by your sandboxed library.
|
||||||
|
|
||||||
|
|
||||||
|
## SAPI Library
|
||||||
|
|
||||||
|
The *SAPI library* is a sandboxed process, which exposes required functionality
|
||||||
|
to the *host code*.
|
||||||
|
|
||||||
|
In order to create it, you'll need your C/C++ library, for example another open
|
||||||
|
source project on GitHub. You will also have to create some supporting code
|
||||||
|
(part of it will be automatically generated). This code will describe which
|
||||||
|
functionality exactly you would like to contain (which library functions), and
|
||||||
|
the [sandbox policies](../sandbox2/docs/getting-started.md#policy) you would
|
||||||
|
like your library to run under.
|
||||||
|
|
||||||
|
All those steps are described in details under [Library](library.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Host Code
|
||||||
|
|
||||||
|
The *host code* is making use of functions exported by your *SAPI Library*.
|
||||||
|
|
||||||
|
It makes calls to sandboxed functions, receives results, and can access memory
|
||||||
|
of a *SAPI library* in order to make copies of remote variables and memory
|
||||||
|
blocks (arrays, structures, protocol buffers, etc.). Those memory blocks
|
||||||
|
can then be accessed by the local process.
|
||||||
|
|
||||||
|
The host code can also copy contents of local memory to the remote process if
|
||||||
|
needed.
|
||||||
|
|
||||||
|
Read about writing host code [here](host-code.md).
|
76
sandboxed_api/docs/host-code.md
Normal file
76
sandboxed_api/docs/host-code.md
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# Host Code
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
The *host code* is the actual code making use of the functionality offered by
|
||||||
|
its contained/isolated/sandboxed counterpart, i.e. a [SAPI Library](library.md).
|
||||||
|
|
||||||
|
Such code implements the logic, that any program making use of a typical library
|
||||||
|
would: it calls functions exported by said library, passing and receiving data
|
||||||
|
to/from it.
|
||||||
|
|
||||||
|
Given that the SAPI Library lives in a separate and contained/sandboxed process,
|
||||||
|
calling such functions directly is not possible. Therefore the SAPI project
|
||||||
|
provides tools which create an API object that proxies accesses to sandboxed
|
||||||
|
libraries.
|
||||||
|
|
||||||
|
More on that can be found under [library](library.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
In order to make sure that host code can access variables and memory blocks in
|
||||||
|
a remote process, SAPI provides a comprehensive set of C++ classes. These try to
|
||||||
|
make the implementation of the main logic code simpler. To do this you will
|
||||||
|
sometimes have to use those objects instead of typical data types known from C.
|
||||||
|
|
||||||
|
For example, instead of an array of three `int`'s, you will instead have to use
|
||||||
|
and pass to sandboxed functions the following object
|
||||||
|
```cpp
|
||||||
|
int arr[3] = {1, 2, 3};
|
||||||
|
sapi::v::Array<int> sarr(arr, ABSL_ARRAYSIZE(arr));
|
||||||
|
```
|
||||||
|
|
||||||
|
[Read more](variables.md) on the internal data representation used in host
|
||||||
|
code.
|
||||||
|
|
||||||
|
|
||||||
|
## Transactions
|
||||||
|
|
||||||
|
When you use a typical library of functions, you do not have to worry about the
|
||||||
|
fact that a call to a library might fail at runtime, as the linker ensures all
|
||||||
|
necessary functions are available after compilation.
|
||||||
|
|
||||||
|
Unfortunately with the SAPI, the sandboxed library lives in a separate process,
|
||||||
|
therefore we need to check for all kinds of problems related to passing such
|
||||||
|
calls via our RPC layer.
|
||||||
|
|
||||||
|
Users of SAPI need to check - in addition to regular errors returned by the
|
||||||
|
native API of a library - for errors returned by the RPC layer. Sometimes these
|
||||||
|
errors might not be interesting, for example when doing bulk processing and you
|
||||||
|
would just restart the sandbox.
|
||||||
|
|
||||||
|
Handling these errors would mean that each call to a SAPI library is followed
|
||||||
|
by an additional check to RPC layer of SAPI. To make handling of such
|
||||||
|
cases easier we have implemented the `::sapi::Transaction` class.
|
||||||
|
|
||||||
|
This module makes sure that all function calls to the sandboxed library were
|
||||||
|
completed without any RPC-level problems, or it will return relevant error.
|
||||||
|
|
||||||
|
Read more about this module under [Transactions](transactions.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Sandbox restarts
|
||||||
|
|
||||||
|
Many sandboxees handle sensitive user input. This data might be at risk when the
|
||||||
|
sandboxee was corrupted at some point and stores data between runs - imagine
|
||||||
|
an Imagemagick sandbox that starts sending out pictures of the previous run. To
|
||||||
|
avoid this we need to stop reusing sandboxes. This can be achieved by restarting
|
||||||
|
the sandboxee with `::sapi::Sandbox::Restart()` or
|
||||||
|
`::sapi::Transaction::Restart()` when using transactions.
|
||||||
|
|
||||||
|
**Restarting the sandboxee will invalidate any references to the sandboxee!**
|
||||||
|
This means passed file descriptors/allocated memory will not exist anymore.
|
||||||
|
|
||||||
|
Note: Restarting the sandboxee takes some time, about *75-80 ms* on modern
|
||||||
|
machines (more if network namespaces are used).
|
29
sandboxed_api/docs/howitworks.md
Normal file
29
sandboxed_api/docs/howitworks.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# How it works
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Sandboxed API project allows to run code of libraries in a sandboxed
|
||||||
|
environment, isolated with the help of [Sandbox2](../sandbox2/README.md).
|
||||||
|
|
||||||
|
Our goal is to provide developers with tools to prepare such libraries for the
|
||||||
|
sandboxing process, as well as necessary APIs to communicate (i.e. make function
|
||||||
|
calls and receive results) with such library.
|
||||||
|
|
||||||
|
All calls to the sandboxed library are passed over our custom RPC implementation
|
||||||
|
to a sandboxed process, and the results are passed back to the caller.
|
||||||
|
|
||||||
|
![SAPI Diagram](images/sapi-overview.png)
|
||||||
|
|
||||||
|
The project also provides [primitives](variables.md) for manual and
|
||||||
|
automatic (based on custom pointer attributes) memory synchronization (arrays,
|
||||||
|
structures) between the SAPI Libraries and the host code.
|
||||||
|
|
||||||
|
A [high-level Transactions API](transactions.md) provides monitoring of SAPI
|
||||||
|
Libraries, and restarts them if they fail (e.g, due to security violations,
|
||||||
|
crashes or resource exhaustion).
|
||||||
|
|
||||||
|
|
||||||
|
## Getting startd
|
||||||
|
|
||||||
|
Read our [Get Started](getting-started.md) page to set up your first Sandboxed
|
||||||
|
API project.
|
BIN
sandboxed_api/docs/images/playing-in-sand.png
Normal file
BIN
sandboxed_api/docs/images/playing-in-sand.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
BIN
sandboxed_api/docs/images/sapi-overview.png
Normal file
BIN
sandboxed_api/docs/images/sapi-overview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
123
sandboxed_api/docs/library.md
Normal file
123
sandboxed_api/docs/library.md
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
# Library
|
||||||
|
|
||||||
|
|
||||||
|
## BUILD.bazel
|
||||||
|
|
||||||
|
Here, you'll prepare a build target, that your [host code](host-code.md)
|
||||||
|
will make use of.
|
||||||
|
|
||||||
|
Start by preparing a [sapi_library()][sapi_library] target in your `BUILD.bazel`
|
||||||
|
file.
|
||||||
|
|
||||||
|
For reference, you can take a peek at a working example from the
|
||||||
|
[zlib example](../examples/zlib/lib/BUILD.bazel).
|
||||||
|
|
||||||
|
```python
|
||||||
|
load(
|
||||||
|
"//sandboxed_api/tools/generator:sapi_generator.bzl",
|
||||||
|
"sapi_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_library(
|
||||||
|
name = "zlib-sapi",
|
||||||
|
srcs = [], # Extra code compiled with the SAPI library
|
||||||
|
hdrs = [] # Leave empty if embedded SAPI libraries are used, and the
|
||||||
|
# default sandbox policy is sufficient.
|
||||||
|
embed = True,
|
||||||
|
functions = [
|
||||||
|
"deflateInit_",
|
||||||
|
"deflate",
|
||||||
|
"deflateEnd",
|
||||||
|
],
|
||||||
|
lib = "@zlib//:zlibonly",
|
||||||
|
lib_name = "Zlib",
|
||||||
|
namespace = "sapi::zlib",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
* **`name`** - name for your SAPI target
|
||||||
|
* **`srcs`** - any additional sources that you'd like to include with your
|
||||||
|
Sandboxed API library - typically, it's not necessary, unless you want to
|
||||||
|
provide your SAPI Library sandbox definition in a .cc file, and not in the
|
||||||
|
`sandbox.h` file.
|
||||||
|
* **`hdrs`** - as with **`srcs`**. Typically your sandbox definition (sandbox.h)
|
||||||
|
should go here, or empty, if embedded SAPI library is used, and the default
|
||||||
|
sandbox policy is sufficient.
|
||||||
|
* **`functions`** - a list of functions that you'd like to use in your host
|
||||||
|
code. Leaving this list empty will try to export and wrap all functions found
|
||||||
|
in the library.
|
||||||
|
* **`embed`** - whether the SAPI library should be embedded inside host code,
|
||||||
|
so the SAPI Sandbox can be initialized with the
|
||||||
|
`::sapi::Sandbox::Sandbox(FileToc*)` constructor.
|
||||||
|
* **`lib`** - (mandatory) the library target you want to sandbox and expose to
|
||||||
|
the host code.
|
||||||
|
* **`lib_name`** - (mandatory) name of the object which is proxying your library
|
||||||
|
functions from the `functions` list. You will call functions from the
|
||||||
|
sandboxed library via this object.
|
||||||
|
* **`input_files`** - list of source files, which SAPI interface generator
|
||||||
|
should scan for library's function declarations. Library's exported headers
|
||||||
|
are always scanned, so `input_files` can usually be left empty.
|
||||||
|
* **`namespace`** - a C++ namespace identifier to place API object defined in
|
||||||
|
`lib_name` into. Defaults to `sapigen`.
|
||||||
|
* **`deps**`** - a list of any additional dependency targets to add. Typically
|
||||||
|
not necessary.
|
||||||
|
* **`header`** - name of the header file to use instead of the generated one.
|
||||||
|
Do not use if you want to auto-generate the code.
|
||||||
|
|
||||||
|
|
||||||
|
## `sapi_library()` Rule Targets
|
||||||
|
|
||||||
|
For the above definition, `sapi_library()` build rule provides the following
|
||||||
|
targets:
|
||||||
|
|
||||||
|
* **`zlib-sapi`** - sandboxed library, substitiution for normal cc_library;
|
||||||
|
consists of **`zlib_sapi.bin`** and sandbox dependencies
|
||||||
|
* **`zlib-sapi.interface`** - generated library interface
|
||||||
|
* **`zlib-sapi.embed`** - `cc_embed_data()` target used to embed sandboxee in
|
||||||
|
the binary. See `bazel/embed_data.bzl`.
|
||||||
|
* **`zlib-sapi.bin`** - sandboxee binary, consists of small communication stub
|
||||||
|
and the library that is being sandboxed.
|
||||||
|
|
||||||
|
|
||||||
|
## Interface Generation
|
||||||
|
|
||||||
|
__`zlib-sapi`__ target creates target library with small communication stub
|
||||||
|
wrapped in [Sandbox2](../sandbox2/README.md). To be able to use the stub and
|
||||||
|
code within the sanbox, you should generate the interface file.
|
||||||
|
|
||||||
|
There are two options:
|
||||||
|
|
||||||
|
1. Add dependency on __`zlib-sapi.interface`__. This will auto-generate a
|
||||||
|
header that you can include in your code - the header name is of the form:
|
||||||
|
__`TARGET_NAME`__`.sapi.h`.
|
||||||
|
2. Run `bazel build TARGET_NAME.interface`, save generated header in your
|
||||||
|
project and include it in the code. You will also need to add the `header`
|
||||||
|
argument to the `sapi_library()` rule to indicate that you will skip code
|
||||||
|
generation.
|
||||||
|
|
||||||
|
|
||||||
|
## Sandbox Description (`sandbox.h`)
|
||||||
|
|
||||||
|
**Note**: If the default SAPI Sandbox policy is sufficient, and the constructor
|
||||||
|
used is **`::sapi::Sandbox::Sandbox(FileToc*)`**, then this file might not be
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
In this step you will prepare the sandbox definition file (typically named
|
||||||
|
`sandbox.h`) for your library.
|
||||||
|
|
||||||
|
The goal of this is to tell the SAPI code where the sandboxed library can be
|
||||||
|
found, and how it should be contained.
|
||||||
|
|
||||||
|
At first, you should tell the SAPI code what your sandboxed library should be
|
||||||
|
allowed to do in terms of security policies and other process constraints. In
|
||||||
|
order to do that, you will have to implement and instantiate an object based on
|
||||||
|
the [::sapi::Sandbox](../sandbox.h) class.
|
||||||
|
|
||||||
|
This object will also specify where your SAPI Library can be found
|
||||||
|
and how it should be executed (though you can depend on default settings).
|
||||||
|
|
||||||
|
A working example of such SAPI object definition file can be found
|
||||||
|
[here](../examples/zlib/lib/sandbox.h).
|
||||||
|
|
||||||
|
In order to familiarize yourself with the Sandbox2 policies, you might want to
|
||||||
|
take a look at the [Sandbox2 documenation](../sandbox2/README.md).
|
52
sandboxed_api/docs/sandbox-overview.md
Normal file
52
sandboxed_api/docs/sandbox-overview.md
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Sandboxing Code
|
||||||
|
|
||||||
|
Sometimes, a piece of code carries a lot of security risk. Examples include:
|
||||||
|
|
||||||
|
* Commercial binary-only code to do document parsing. Document parsing often
|
||||||
|
goes wrong, and binary-only means no opportunity to fix it up.
|
||||||
|
* A web browser's core HTML parsing and rendering. This is such a large amount
|
||||||
|
of code that there will be security bugs.
|
||||||
|
* A JavaScript engine in Java. Accidents here would permit arbitrary calls to
|
||||||
|
Java methods.
|
||||||
|
|
||||||
|
Where a piece of code is very risky, and directly exposed to untrusted users
|
||||||
|
and untrusted input, it is sometimes desirable to sandbox this code. The hardest
|
||||||
|
thing about sandboxing is making the call whether the risk warrants the effort
|
||||||
|
to sandbox.
|
||||||
|
|
||||||
|
There are many approaches to sandboxing, including virtualization, jail
|
||||||
|
environments, network segregation and restricting the permissions code runs
|
||||||
|
with. This page covers technologies available to do the latter: restrict the
|
||||||
|
permission code runs with. See the following depending on which technology you
|
||||||
|
are using:
|
||||||
|
|
||||||
|
## General Sandboxing
|
||||||
|
|
||||||
|
Project/technology | Description
|
||||||
|
-------------------------------------------|------------
|
||||||
|
[Sandbox2](../sandbox2/README.md) | Linux sandboxing using namespaces, resource limits and seccomp-bpf syscall filters. Provides the underlying sandboxing technology for Sandboxed API.
|
||||||
|
[gVisor](https://github.com/google/gvisor) | Uses hardware virtualization and a small syscall emulation layer implemented in Go.
|
||||||
|
|
||||||
|
|
||||||
|
## Sandbox command-line tools
|
||||||
|
|
||||||
|
Project/technology | Description
|
||||||
|
---------------------|------------
|
||||||
|
[Firejail](https://github.com/netblue30/firejail) | Lightweight sandboxing tool implemented as a SUID program with minimal dependencies.
|
||||||
|
[Minijail](https://android.googlesource.com/platform/external/minijail/) | The sandboxing and containment tool used in Chrome OS and Android. Provides an executable and a library that can be used to launch and sandbox other programs and code.
|
||||||
|
[NSJail](nsjail.com) | Process isolation for Linux using namespaces, resource limits and seccomp-bpf syscall filters. Can optionally make use of [Kafel](https://github.com/google/kafel/), a custom domain specific language, for specifying syscall policies.
|
||||||
|
|
||||||
|
|
||||||
|
## C/C++
|
||||||
|
|
||||||
|
Project/technology | Description
|
||||||
|
-------------------------|------------
|
||||||
|
[Sandboxed API](..) | Reusable sandboxes for C/C++ libraries using Sandbox2.
|
||||||
|
(Portable) Native Client | **(Deprecated)** Powerful technique to sandbox C/C++ binaries by compiling to a restricted subset of x86 (NaCl)/LLVM bytecode (PNaCl).
|
||||||
|
|
||||||
|
|
||||||
|
## Graphical/Desktop Applications
|
||||||
|
|
||||||
|
Project/technology | Description
|
||||||
|
----------------------------------------------|------------
|
||||||
|
[Flatpak](https://github.com/flatpak/flatpak) | Built on top of [Bubblewrap](https://github.com/projectatomic/bubblewrap), provides sandboxing for Linux desktop applications. Puts an emphasis on packaging and distribution of native apps.
|
69
sandboxed_api/docs/variables.md
Normal file
69
sandboxed_api/docs/variables.md
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# Variables
|
||||||
|
|
||||||
|
Typically, you'll be able to use native C-types to deal with the SAPI Library,
|
||||||
|
but sometimes some special types will be required. This mainly happens when
|
||||||
|
passing pointers to simple types, and pointers to memory blocks (structures,
|
||||||
|
arrays). Because you operate on local process memory (of the host code), when
|
||||||
|
calling a function taking a pointer, it must be converted into a corresponding
|
||||||
|
pointer inside the sandboxed process (SAPI Library) memory.
|
||||||
|
|
||||||
|
Take a look at the [SAPI directory](..). The `var_*.h` files provide classes
|
||||||
|
and templates representing various types of data, e.g. `::sapi::v::UChar`
|
||||||
|
represents well-known `unsigned char` while `::sapi::v::Array<int>` represents
|
||||||
|
an array of integers (`int[]`).
|
||||||
|
|
||||||
|
|
||||||
|
## Pointers
|
||||||
|
|
||||||
|
When creating your host code, you'll be generally using functions exported by
|
||||||
|
an auto-generated SAPI interface header file from your SAPI Library. Most of
|
||||||
|
them will take simple types (or typedef'd types), but when a pointer is needed,
|
||||||
|
you need to wrap it with the `::sapi::v::Ptr` template class.
|
||||||
|
|
||||||
|
Most types that you will use, provide the following methods:
|
||||||
|
|
||||||
|
* `::PtrNone()`: this pointer, when passed to the SAPI Library function,
|
||||||
|
doesn't synchronize the underlying memory between the host code process and
|
||||||
|
the SAPI Library process.
|
||||||
|
* `::PtrBefore()`: when passed to the SAPI Library function, will synchronize
|
||||||
|
memory of the object it points to, before the call takes place. This means,
|
||||||
|
that the local memory of the pointed variable will be transferred to the
|
||||||
|
SAPI Library process before the call is initiated.
|
||||||
|
* `::PtrAfter()`: this pointer will synchronize memory of the object it points
|
||||||
|
to, after the call has taken place. This means, that the remote memory of a
|
||||||
|
pointed variable will be transferred to the host code process' memory, after
|
||||||
|
the call has been completed.
|
||||||
|
* `::PtrBoth()`: combines the functionality of both `::PtrBefore()` and
|
||||||
|
`::PtrAfter()`
|
||||||
|
|
||||||
|
|
||||||
|
## Structures
|
||||||
|
|
||||||
|
When a pointer to a structure is used inside a call to a SAPI Library, that
|
||||||
|
structure needs to created with the `::sapi::v::Struct` template. You can use
|
||||||
|
the `PtrNone()`/`Before()`/`After()`/`Both()` methods of this template to obtain
|
||||||
|
a relevant `::sapi::v::Ptr` object that can be used in SAPI Library function
|
||||||
|
calls.
|
||||||
|
|
||||||
|
|
||||||
|
## Arrays
|
||||||
|
|
||||||
|
The `::sapi::v::Array` template allow to wrap both existing arrays of elements,
|
||||||
|
as well as dynamically create one for you (please take a look at its
|
||||||
|
constructor to decide which one you would like to use).
|
||||||
|
|
||||||
|
The use of pointers is analogous to [Structures](#structures).
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Our canonical [sum library](../examples/sum/main_sum.cc) demonstrates use of
|
||||||
|
pointers to call sandboxed functions in its corresponding SAPI Library.
|
||||||
|
|
||||||
|
You might also want to take a look at the [Examples](examples.md) page to
|
||||||
|
familiarize yourself with other working examples of libraries sandboxed
|
||||||
|
with SAPI.
|
||||||
|
|
||||||
|
* [sum library](../examples/sum/main_sum.cc)
|
||||||
|
* [stringop](../examples/stringop/main_stringop.cc)
|
||||||
|
* [zlib](../examples/zlib/main_zlib.cc)
|
106
sandboxed_api/embed_file.cc
Normal file
106
sandboxed_api/embed_file.cc
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sandboxed_api/embed_file.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/fileops.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/strerror.h"
|
||||||
|
#include "sandboxed_api/util/canonical_errors.h"
|
||||||
|
#include "sandboxed_api/util/status.h"
|
||||||
|
|
||||||
|
namespace file_util = ::sandbox2::file_util;
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
|
||||||
|
EmbedFile* EmbedFile::GetEmbedFileSingleton() {
|
||||||
|
static auto* embed_file_instance = new EmbedFile{};
|
||||||
|
return embed_file_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EmbedFile::CreateFdForFileToc(const FileToc* toc) {
|
||||||
|
// Create a memfd/temp file and write contents of the SAPI library to it.
|
||||||
|
int embed_fd = -1;
|
||||||
|
if (!sandbox2::util::CreateMemFd(&embed_fd, toc->name)) {
|
||||||
|
LOG(ERROR) << "Couldn't create a temporary file for TOC name '" << toc->name
|
||||||
|
<< "'";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_util::fileops::WriteToFD(embed_fd, toc->data, toc->size)) {
|
||||||
|
PLOG(ERROR) << "Couldn't write SAPI embed file '" << toc->name
|
||||||
|
<< "' to memfd file";
|
||||||
|
close(embed_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the underlying file non-writeable.
|
||||||
|
if (fchmod(embed_fd,
|
||||||
|
S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) {
|
||||||
|
PLOG(ERROR) << "Could't make FD=" << embed_fd << " RX-only";
|
||||||
|
close(embed_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EmbedFile::GetFdForFileToc(const FileToc* toc) {
|
||||||
|
// Access to file_tocs_ must be guarded.
|
||||||
|
absl::MutexLock lock{&file_tocs_mutex_};
|
||||||
|
|
||||||
|
// If a file-descriptor for this toc already exists, just return it.
|
||||||
|
auto entry = file_tocs_.find(toc);
|
||||||
|
if (entry != file_tocs_.end()) {
|
||||||
|
VLOG(3) << "Returning pre-existing embed file entry for '" << toc->name
|
||||||
|
<< "', fd: " << entry->second << " (orig name:'"
|
||||||
|
<< entry->first->name << "')";
|
||||||
|
return entry->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
int embed_fd = CreateFdForFileToc(toc);
|
||||||
|
if (embed_fd == -1) {
|
||||||
|
LOG(ERROR) << "Cannot create a file for FileTOC: '" << toc->name << "'";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
VLOG(1) << "Created new embed file entry for '" << toc->name
|
||||||
|
<< "' with fd: " << embed_fd;
|
||||||
|
|
||||||
|
file_tocs_[toc] = embed_fd;
|
||||||
|
return embed_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EmbedFile::GetDupFdForFileToc(const FileToc* toc) {
|
||||||
|
int fd = GetFdForFileToc(toc);
|
||||||
|
if (fd == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fd = dup(fd);
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOG(ERROR) << "dup(" << fd << ") failed";
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sapi
|
58
sandboxed_api/embed_file.h
Normal file
58
sandboxed_api/embed_file.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_EMBED_FILE_H_
|
||||||
|
#define SANDBOXED_API_EMBED_FILE_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "sandboxed_api/file_toc.h"
|
||||||
|
#include "absl/container/flat_hash_map.h"
|
||||||
|
#include "absl/synchronization/mutex.h"
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
|
||||||
|
// The class provides primitives for converting FileToc structures into
|
||||||
|
// executable files.
|
||||||
|
class EmbedFile {
|
||||||
|
public:
|
||||||
|
EmbedFile() = default;
|
||||||
|
|
||||||
|
EmbedFile(const EmbedFile&) = delete;
|
||||||
|
EmbedFile& operator=(const EmbedFile&) = delete;
|
||||||
|
|
||||||
|
// Returns the pointer to the per-process EmbedFile object.
|
||||||
|
static EmbedFile* GetEmbedFileSingleton();
|
||||||
|
|
||||||
|
// Returns a file-descriptor for a given FileToc.
|
||||||
|
int GetFdForFileToc(const FileToc* toc);
|
||||||
|
|
||||||
|
// Returns a duplicated file-descriptor for a given FileToc.
|
||||||
|
int GetDupFdForFileToc(const FileToc* toc);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Creates an executable file for a given FileToc, and return its
|
||||||
|
// file-descriptors (-1 in case of errors).
|
||||||
|
static int CreateFdForFileToc(const FileToc* toc);
|
||||||
|
|
||||||
|
// List of File TOCs and corresponding file-descriptors.
|
||||||
|
absl::flat_hash_map<const FileToc*, int> file_tocs_
|
||||||
|
GUARDED_BY(file_tocs_mutex_);
|
||||||
|
absl::Mutex file_tocs_mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sapi
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_EMBED_FILE_H_
|
35
sandboxed_api/examples/stringop/BUILD.bazel
Normal file
35
sandboxed_api/examples/stringop/BUILD.bazel
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Description: Examples using dynamic length structures for Sandboxed-API
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "main_stringop",
|
||||||
|
srcs = ["main_stringop.cc"],
|
||||||
|
tags = ["local"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api:sapi",
|
||||||
|
"//sandboxed_api:vars",
|
||||||
|
"//sandboxed_api/examples/stringop/lib:stringop-sapi",
|
||||||
|
"//sandboxed_api/examples/stringop/lib:stringop_params_proto_cc",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/time",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
58
sandboxed_api/examples/stringop/lib/BUILD.bazel
Normal file
58
sandboxed_api/examples/stringop/lib/BUILD.bazel
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
load("//sandboxed_api/bazel:proto.bzl", "sapi_proto_library")
|
||||||
|
load("//sandboxed_api/bazel:sapi.bzl", "sapi_library")
|
||||||
|
|
||||||
|
sapi_proto_library(
|
||||||
|
name = "stringop_params_proto",
|
||||||
|
srcs = ["stringop_params.proto"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "stringop",
|
||||||
|
srcs = ["stringop.cc"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":stringop_params_proto_cc",
|
||||||
|
"//sandboxed_api:lenval_core",
|
||||||
|
],
|
||||||
|
alwayslink = 1, # All functions are linked into dependent binaries
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_library(
|
||||||
|
name = "stringop-sapi",
|
||||||
|
srcs = [],
|
||||||
|
hdrs = ["sandbox.h"],
|
||||||
|
embed = True,
|
||||||
|
functions = [
|
||||||
|
"duplicate_string",
|
||||||
|
"reverse_string",
|
||||||
|
"pb_duplicate_string",
|
||||||
|
"pb_reverse_string",
|
||||||
|
"nop",
|
||||||
|
"violate",
|
||||||
|
],
|
||||||
|
input_files = [
|
||||||
|
"stringop.cc",
|
||||||
|
],
|
||||||
|
lib = ":stringop",
|
||||||
|
lib_name = "Stringop",
|
||||||
|
namespace = "",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [":stringop_params_proto_cc"],
|
||||||
|
)
|
54
sandboxed_api/examples/stringop/lib/sandbox.h
Normal file
54
sandboxed_api/examples/stringop/lib/sandbox.h
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_EXAMPLES_STRINGOP_LIB_SANDBOX_H_
|
||||||
|
#define SANDBOXED_API_EXAMPLES_STRINGOP_LIB_SANDBOX_H_
|
||||||
|
|
||||||
|
#include <linux/audit.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
|
#include "sandboxed_api/examples/stringop/lib/stringop-sapi.sapi.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
|
||||||
|
class StringopSapiSandbox : public StringopSandbox {
|
||||||
|
public:
|
||||||
|
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
|
||||||
|
sandbox2::PolicyBuilder*) override {
|
||||||
|
// Return a new policy.
|
||||||
|
return sandbox2::PolicyBuilder()
|
||||||
|
.AllowRead()
|
||||||
|
.AllowWrite()
|
||||||
|
.AllowOpen()
|
||||||
|
.AllowSystemMalloc()
|
||||||
|
.AllowHandleSignals()
|
||||||
|
.AllowExit()
|
||||||
|
.AllowStat()
|
||||||
|
.AllowTime()
|
||||||
|
.AllowSyscalls({
|
||||||
|
__NR_recvmsg,
|
||||||
|
__NR_sendmsg,
|
||||||
|
__NR_lseek,
|
||||||
|
__NR_nanosleep,
|
||||||
|
__NR_futex,
|
||||||
|
__NR_gettid,
|
||||||
|
__NR_close,
|
||||||
|
})
|
||||||
|
.AddFile("/etc/localtime")
|
||||||
|
.EnableNamespaces()
|
||||||
|
.BuildOrDie();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_EXAMPLES_STRINGOP_LIB_SANDBOX_H_
|
78
sandboxed_api/examples/stringop/lib/stringop.cc
Normal file
78
sandboxed_api/examples/stringop/lib/stringop.cc
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <sys/ptrace.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "sandboxed_api/examples/stringop/lib/stringop_params.pb.h"
|
||||||
|
#include "sandboxed_api/lenval_core.h"
|
||||||
|
|
||||||
|
// Protobuf examples.
|
||||||
|
extern "C" int pb_reverse_string(stringop::StringReverse* pb) {
|
||||||
|
if (pb->payload_case() == pb->kInput) {
|
||||||
|
std::string output = pb->input();
|
||||||
|
std::reverse(output.begin(), output.end());
|
||||||
|
pb->set_output(output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int pb_duplicate_string(stringop::StringDuplication* pb) {
|
||||||
|
if (pb->payload_case() == pb->kInput) {
|
||||||
|
auto output = pb->input();
|
||||||
|
pb->set_output(output + output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Examples on raw data - both allocate and replace the data pointer.
|
||||||
|
extern "C" int reverse_string(sapi::LenValStruct* input) {
|
||||||
|
char* new_buf = static_cast<char*>(malloc(input->size));
|
||||||
|
const char* src_buf = static_cast<const char*>(input->data);
|
||||||
|
input->size = input->size;
|
||||||
|
for (size_t i = 0; i < input->size; i++) {
|
||||||
|
new_buf[i] = src_buf[input->size - i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free old value.
|
||||||
|
free(input->data);
|
||||||
|
// Replace pointer to our new std::string.
|
||||||
|
input->data = new_buf;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" int duplicate_string(sapi::LenValStruct* input) {
|
||||||
|
char* new_buf = static_cast<char*>(malloc(2 * input->size));
|
||||||
|
const char* src_buf = static_cast<const char*>(input->data);
|
||||||
|
|
||||||
|
for (size_t c = 0; c < 2; c++) {
|
||||||
|
for (size_t i = 0; i < input->size; i++) {
|
||||||
|
new_buf[i + input->size * c] = src_buf[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free old value.
|
||||||
|
free(input->data);
|
||||||
|
// Update structure.
|
||||||
|
input->size = 2 * input->size;
|
||||||
|
input->data = new_buf;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void nop() {}
|
||||||
|
|
||||||
|
extern "C" void violate() { ptrace((__ptrace_request)990, 991, 992, 993); }
|
31
sandboxed_api/examples/stringop/lib/stringop_params.proto
Normal file
31
sandboxed_api/examples/stringop/lib/stringop_params.proto
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package stringop;
|
||||||
|
|
||||||
|
message StringReverse {
|
||||||
|
oneof payload {
|
||||||
|
string input = 1;
|
||||||
|
string output = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message StringDuplication {
|
||||||
|
oneof payload {
|
||||||
|
string input = 1;
|
||||||
|
string output = 2;
|
||||||
|
}
|
||||||
|
}
|
139
sandboxed_api/examples/stringop/main_stringop.cc
Normal file
139
sandboxed_api/examples/stringop/main_stringop.cc
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/time/time.h"
|
||||||
|
#include "sandboxed_api/examples/stringop/lib/sandbox.h"
|
||||||
|
#include "sandboxed_api/examples/stringop/lib/stringop-sapi.sapi.h"
|
||||||
|
#include "sandboxed_api/examples/stringop/lib/stringop_params.pb.h"
|
||||||
|
#include "sandboxed_api/transaction.h"
|
||||||
|
#include "sandboxed_api/util/status_matchers.h"
|
||||||
|
#include "sandboxed_api/vars.h"
|
||||||
|
#include "sandboxed_api/util/canonical_errors.h"
|
||||||
|
#include "sandboxed_api/util/status.h"
|
||||||
|
|
||||||
|
using ::sapi::IsOk;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Tests using the simple transaction (and function pointers):
|
||||||
|
TEST(StringopTest, ProtobufStringDuplication) {
|
||||||
|
sapi::BasicTransaction st(absl::make_unique<StringopSapiSandbox>());
|
||||||
|
EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status {
|
||||||
|
StringopApi f(sandbox);
|
||||||
|
stringop::StringDuplication proto;
|
||||||
|
proto.set_input("Hello");
|
||||||
|
sapi::v::Proto<stringop::StringDuplication> pp(proto);
|
||||||
|
SAPI_ASSIGN_OR_RETURN(int v, f.pb_duplicate_string(pp.PtrBoth()));
|
||||||
|
TRANSACTION_FAIL_IF_NOT(v, "pb_duplicate_string failed");
|
||||||
|
auto pb_result = pp.GetProtoCopy();
|
||||||
|
TRANSACTION_FAIL_IF_NOT(pb_result, "Could not deserialize pb result");
|
||||||
|
LOG(INFO) << "Result PB: " << pb_result->DebugString();
|
||||||
|
TRANSACTION_FAIL_IF_NOT(pb_result->output() == "HelloHello",
|
||||||
|
"Incorrect output");
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}),
|
||||||
|
IsOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StringopTest, ProtobufStringReversal) {
|
||||||
|
sapi::BasicTransaction st(absl::make_unique<StringopSapiSandbox>());
|
||||||
|
EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status {
|
||||||
|
StringopApi f(sandbox);
|
||||||
|
stringop::StringReverse proto;
|
||||||
|
proto.set_input("Hello");
|
||||||
|
sapi::v::Proto<stringop::StringReverse> pp(proto);
|
||||||
|
SAPI_ASSIGN_OR_RETURN(int v, f.pb_reverse_string(pp.PtrBoth()));
|
||||||
|
TRANSACTION_FAIL_IF_NOT(v, "pb_reverse_string failed");
|
||||||
|
auto pb_result = pp.GetProtoCopy();
|
||||||
|
TRANSACTION_FAIL_IF_NOT(pb_result, "Could not deserialize pb result");
|
||||||
|
LOG(INFO) << "Result PB: " << pb_result->DebugString();
|
||||||
|
TRANSACTION_FAIL_IF_NOT(pb_result->output() == "olleH", "Incorrect output");
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}),
|
||||||
|
IsOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests using raw dynamic buffers.
|
||||||
|
TEST(StringopTest, RawStringDuplication) {
|
||||||
|
sapi::BasicTransaction st(absl::make_unique<StringopSapiSandbox>());
|
||||||
|
EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status {
|
||||||
|
StringopApi f(sandbox);
|
||||||
|
sapi::v::LenVal param("0123456789", 10);
|
||||||
|
SAPI_ASSIGN_OR_RETURN(int return_value, f.duplicate_string(param.PtrBoth()));
|
||||||
|
TRANSACTION_FAIL_IF_NOT(return_value == 1,
|
||||||
|
"duplicate_string() returned incorrect value");
|
||||||
|
TRANSACTION_FAIL_IF_NOT(param.GetDataSize() == 20,
|
||||||
|
"duplicate_string() did not return enough data");
|
||||||
|
absl::string_view data(reinterpret_cast<const char*>(param.GetData()),
|
||||||
|
param.GetDataSize());
|
||||||
|
TRANSACTION_FAIL_IF_NOT(
|
||||||
|
data == "01234567890123456789",
|
||||||
|
"duplicate_string() did not return the expected data");
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}),
|
||||||
|
IsOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StringopTest, RawStringReversal) {
|
||||||
|
sapi::BasicTransaction st(absl::make_unique<StringopSapiSandbox>());
|
||||||
|
EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status {
|
||||||
|
StringopApi f(sandbox);
|
||||||
|
sapi::v::LenVal param("0123456789", 10);
|
||||||
|
{
|
||||||
|
SAPI_ASSIGN_OR_RETURN(int return_value, f.reverse_string(param.PtrBoth()));
|
||||||
|
TRANSACTION_FAIL_IF_NOT(return_value == 1,
|
||||||
|
"reverse_string() returned incorrect value");
|
||||||
|
TRANSACTION_FAIL_IF_NOT(param.GetDataSize() == 10,
|
||||||
|
"reverse_string() did not return enough data");
|
||||||
|
absl::string_view data(reinterpret_cast<const char*>(param.GetData()),
|
||||||
|
param.GetDataSize());
|
||||||
|
TRANSACTION_FAIL_IF_NOT(
|
||||||
|
data == "9876543210",
|
||||||
|
"reverse_string() did not return the expected data");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Let's call it again with different data as argument, reusing the
|
||||||
|
// existing LenVal object.
|
||||||
|
SAPI_RETURN_IF_ERROR(param.ResizeData(sandbox->GetRpcChannel(), 16));
|
||||||
|
memcpy(param.GetData() + 10, "ABCDEF", 6);
|
||||||
|
TRANSACTION_FAIL_IF_NOT(param.GetDataSize() == 16,
|
||||||
|
"Resize did not behave correctly");
|
||||||
|
absl::string_view data(reinterpret_cast<const char*>(param.GetData()),
|
||||||
|
param.GetDataSize());
|
||||||
|
TRANSACTION_FAIL_IF_NOT(data == "9876543210ABCDEF",
|
||||||
|
"Data not as expected");
|
||||||
|
SAPI_ASSIGN_OR_RETURN(int return_value, f.reverse_string(param.PtrBoth()));
|
||||||
|
TRANSACTION_FAIL_IF_NOT(return_value == 1,
|
||||||
|
"reverse_string() returned incorrect value");
|
||||||
|
data = absl::string_view(reinterpret_cast<const char*>(param.GetData()),
|
||||||
|
param.GetDataSize());
|
||||||
|
TRANSACTION_FAIL_IF_NOT(
|
||||||
|
data == "FEDCBA0123456789",
|
||||||
|
"reverse_string() did not return the expected data");
|
||||||
|
}
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}),
|
||||||
|
IsOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
31
sandboxed_api/examples/sum/BUILD.bazel
Normal file
31
sandboxed_api/examples/sum/BUILD.bazel
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
# A quick'n'dirty testing binary
|
||||||
|
cc_binary(
|
||||||
|
name = "main_sum",
|
||||||
|
srcs = ["main_sum.cc"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api:sapi",
|
||||||
|
"//sandboxed_api:vars",
|
||||||
|
"//sandboxed_api/examples/sum/lib:sum-sapi",
|
||||||
|
"//sandboxed_api/examples/sum/lib:sum_params_proto_cc",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
74
sandboxed_api/examples/sum/lib/BUILD.bazel
Normal file
74
sandboxed_api/examples/sum/lib/BUILD.bazel
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
load("//sandboxed_api/bazel:sapi.bzl", "sapi_library")
|
||||||
|
load(
|
||||||
|
"//sandboxed_api/bazel:proto.bzl",
|
||||||
|
"sapi_proto_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_proto_library(
|
||||||
|
name = "sum_params_proto",
|
||||||
|
srcs = ["sum_params.proto"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
alwayslink = 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "sum",
|
||||||
|
srcs = [
|
||||||
|
"sum.c",
|
||||||
|
"sum_cpp.cc",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":sum_params_proto_cc",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
],
|
||||||
|
alwayslink = 1, # All functions are linked into depending binaries
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_library(
|
||||||
|
name = "sum-sapi",
|
||||||
|
srcs = [],
|
||||||
|
hdrs = ["sandbox.h"],
|
||||||
|
embed = True,
|
||||||
|
functions = [
|
||||||
|
"sum",
|
||||||
|
"sums",
|
||||||
|
"addf",
|
||||||
|
"sub",
|
||||||
|
"mul",
|
||||||
|
"divs",
|
||||||
|
"muld",
|
||||||
|
"crash",
|
||||||
|
"violate",
|
||||||
|
"sumarr",
|
||||||
|
"testptr",
|
||||||
|
"read_int",
|
||||||
|
"sleep_for_sec",
|
||||||
|
"sumproto",
|
||||||
|
],
|
||||||
|
input_files = [
|
||||||
|
"sum.c",
|
||||||
|
"sum_cpp.cc",
|
||||||
|
],
|
||||||
|
lib = ":sum",
|
||||||
|
lib_name = "Sum",
|
||||||
|
namespace = "",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [":sum_params_proto_cc"],
|
||||||
|
)
|
54
sandboxed_api/examples/sum/lib/sandbox.h
Normal file
54
sandboxed_api/examples/sum/lib/sandbox.h
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_EXAMPLES_SUM_LIB_SANDBOX_H_
|
||||||
|
#define SANDBOXED_API_EXAMPLES_SUM_LIB_SANDBOX_H_
|
||||||
|
|
||||||
|
#include <linux/audit.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
|
#include "sandboxed_api/examples/sum/lib/sum-sapi.sapi.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
|
||||||
|
class SumSapiSandbox : public SumSandbox {
|
||||||
|
public:
|
||||||
|
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
|
||||||
|
sandbox2::PolicyBuilder*) override {
|
||||||
|
// Return a new policy.
|
||||||
|
return sandbox2::PolicyBuilder()
|
||||||
|
.AllowRead()
|
||||||
|
.AllowWrite()
|
||||||
|
.AllowOpen()
|
||||||
|
.AllowSystemMalloc()
|
||||||
|
.AllowHandleSignals()
|
||||||
|
.AllowExit()
|
||||||
|
.AllowStat()
|
||||||
|
.AllowTime()
|
||||||
|
.AllowSyscalls({
|
||||||
|
__NR_recvmsg,
|
||||||
|
__NR_sendmsg,
|
||||||
|
__NR_lseek,
|
||||||
|
__NR_nanosleep,
|
||||||
|
__NR_futex,
|
||||||
|
__NR_gettid,
|
||||||
|
__NR_close,
|
||||||
|
})
|
||||||
|
.AddFile("/etc/localtime")
|
||||||
|
.EnableNamespaces()
|
||||||
|
.BuildOrDie();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_EXAMPLES_SUM_LIB_SANDBOX_H_
|
97
sandboxed_api/examples/sum/lib/sum.c
Normal file
97
sandboxed_api/examples/sum/lib/sum.c
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/ptrace.h>
|
||||||
|
|
||||||
|
int sumsymbol = 5;
|
||||||
|
|
||||||
|
typedef struct sum_params_s {
|
||||||
|
int a;
|
||||||
|
int b;
|
||||||
|
int ret;
|
||||||
|
} sum_params;
|
||||||
|
|
||||||
|
extern int ftest(FILE *f) {
|
||||||
|
return f->_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int sum(int a, int b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void sums(sum_params* params) {
|
||||||
|
params->ret = params->a + params->b;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern long double addf(float a, double b, long double c) {
|
||||||
|
return a + b + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int sub(int a, int b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int mul(int a, int b) {
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int divs(int a, int b) {
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern double muld(double a, float b) {
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void crash(void) {
|
||||||
|
void(*die)() = (void(*)())(0x0000dead);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void violate(void) {
|
||||||
|
ptrace(990, 991, 992, 993);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int sumarr(int* input, size_t nelem) {
|
||||||
|
int s = 0, i;
|
||||||
|
for (i = 0; i < nelem; i++) {
|
||||||
|
s += input[i];
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void testptr(void *ptr) {
|
||||||
|
if (ptr) {
|
||||||
|
puts("Is Not a NULL-ptr");
|
||||||
|
} else {
|
||||||
|
puts("Is a NULL-ptr");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int read_int(int fd) {
|
||||||
|
char buf[10] = {0};
|
||||||
|
int ret = read(fd, buf, sizeof(buf) - 1);
|
||||||
|
if(ret > 0) {
|
||||||
|
ret = atoi(buf);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void sleep_for_sec(int sec) {
|
||||||
|
sleep(sec);
|
||||||
|
}
|
21
sandboxed_api/examples/sum/lib/sum_cpp.cc
Normal file
21
sandboxed_api/examples/sum/lib/sum_cpp.cc
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/examples/sum/lib/sum_params.pb.h"
|
||||||
|
|
||||||
|
extern "C" int sumproto(const sumsapi::SumParamsProto* params) {
|
||||||
|
LOG(INFO) << "Param is " << params->DebugString();
|
||||||
|
return params->a() + params->b() + params->c();
|
||||||
|
}
|
23
sandboxed_api/examples/sum/lib/sum_params.proto
Normal file
23
sandboxed_api/examples/sum/lib/sum_params.proto
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package sumsapi;
|
||||||
|
|
||||||
|
message SumParamsProto {
|
||||||
|
optional int32 a = 1 [default = 0];
|
||||||
|
optional int32 b = 2 [default = 0];
|
||||||
|
optional int32 c = 3 [default = 0];
|
||||||
|
}
|
286
sandboxed_api/examples/sum/main_sum.cc
Normal file
286
sandboxed_api/examples/sum/main_sum.cc
Normal file
|
@ -0,0 +1,286 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "sandboxed_api/examples/sum/lib/sandbox.h"
|
||||||
|
#include "sandboxed_api/examples/sum/lib/sum-sapi.sapi.h"
|
||||||
|
#include "sandboxed_api/examples/sum/lib/sum_params.pb.h"
|
||||||
|
#include "sandboxed_api/transaction.h"
|
||||||
|
#include "sandboxed_api/vars.h"
|
||||||
|
#include "sandboxed_api/util/canonical_errors.h"
|
||||||
|
#include "sandboxed_api/util/status.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class SumParams : public sapi::v::Struct<sum_params> {};
|
||||||
|
|
||||||
|
class SumTransaction : public sapi::Transaction {
|
||||||
|
public:
|
||||||
|
SumTransaction(std::unique_ptr<sapi::Sandbox> sandbox, bool crash,
|
||||||
|
bool violate, bool time_out)
|
||||||
|
: sapi::Transaction(std::move(sandbox)),
|
||||||
|
crash_(crash),
|
||||||
|
violate_(violate),
|
||||||
|
time_out_(time_out) {
|
||||||
|
sapi::Transaction::SetTimeLimit(kTimeOutVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Default timeout value for each transaction run.
|
||||||
|
const time_t kTimeOutVal = 2;
|
||||||
|
// Should the sandboxee crash at some point?
|
||||||
|
bool crash_;
|
||||||
|
// Should the sandboxee invoke a disallowed syscall?
|
||||||
|
bool violate_;
|
||||||
|
// Should the sandboxee time_out_?
|
||||||
|
bool time_out_;
|
||||||
|
|
||||||
|
// The main processing function.
|
||||||
|
sapi::Status Main() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
sapi::Status SumTransaction::Main() {
|
||||||
|
SumApi f(GetSandbox());
|
||||||
|
SAPI_ASSIGN_OR_RETURN(int v, f.sum(1000, 337));
|
||||||
|
LOG(INFO) << "1000 + 337 = " << v;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(v == 1337, "1000 + 337 != 1337");
|
||||||
|
|
||||||
|
// Sums two int's held in a structure.
|
||||||
|
SumParams params;
|
||||||
|
params.mutable_data()->a = 1111;
|
||||||
|
params.mutable_data()->b = 222;
|
||||||
|
params.mutable_data()->ret = 0;
|
||||||
|
SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
|
||||||
|
LOG(INFO) << "1111 + 222 = " << params.data().ret;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(params.data().ret == 1333, "1111 + 222 != 1333");
|
||||||
|
|
||||||
|
params.mutable_data()->b = -1000;
|
||||||
|
SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
|
||||||
|
LOG(INFO) << "1111 - 1000 = " << params.data().ret;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(params.data().ret == 111, "1111 - 1000 != 111");
|
||||||
|
|
||||||
|
// Without the wrapper class for struct.
|
||||||
|
sapi::v::Struct<sum_params> p;
|
||||||
|
p.mutable_data()->a = 1234;
|
||||||
|
p.mutable_data()->b = 5678;
|
||||||
|
p.mutable_data()->ret = 0;
|
||||||
|
SAPI_RETURN_IF_ERROR(f.sums(p.PtrBoth()));
|
||||||
|
LOG(INFO) << "1234 + 5678 = " << p.data().ret;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(p.data().ret == 6912, "1234 + 5678 != 6912");
|
||||||
|
|
||||||
|
// Gets symbol address and prints its value.
|
||||||
|
int* ssaddr;
|
||||||
|
SAPI_RETURN_IF_ERROR(
|
||||||
|
GetSandbox()->Symbol("sumsymbol", reinterpret_cast<void**>(&ssaddr)));
|
||||||
|
sapi::v::Int sumsymbol;
|
||||||
|
sumsymbol.SetRemote(ssaddr);
|
||||||
|
SAPI_RETURN_IF_ERROR(GetSandbox()->TransferFromSandboxee(&sumsymbol));
|
||||||
|
LOG(INFO) << "sumsymbol value (exp: 5): " << sumsymbol.GetValue()
|
||||||
|
<< ", address: " << ssaddr;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(sumsymbol.GetValue() == 5,
|
||||||
|
"sumsymbol.GetValue() != 5");
|
||||||
|
|
||||||
|
// Sums all int's inside an array.
|
||||||
|
int arr[10];
|
||||||
|
sapi::v::Array<int> iarr(arr, ABSL_ARRAYSIZE(arr));
|
||||||
|
for (size_t i = 0; i < ABSL_ARRAYSIZE(arr); i++) {
|
||||||
|
iarr[i] = i;
|
||||||
|
}
|
||||||
|
SAPI_ASSIGN_OR_RETURN(v, f.sumarr(iarr.PtrBefore(), iarr.GetNElem()));
|
||||||
|
LOG(INFO) << "Sum(iarr, 10 elem, from 0 to 9, exp: 45) = " << v;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(v == 45, "Sum(iarr, 10 elem, from 0 to 9) != 45");
|
||||||
|
|
||||||
|
float a = 0.99999f;
|
||||||
|
double b = 1.5423432l;
|
||||||
|
long double c = 1.1001L;
|
||||||
|
SAPI_ASSIGN_OR_RETURN(long double r, f.addf(a, b, c));
|
||||||
|
LOG(INFO) << "Addf(" << a << ", " << b << ", " << c << ") = " << r;
|
||||||
|
// TODO(szwl): floating point comparison.
|
||||||
|
|
||||||
|
// Prints "Hello World!!!" via puts()
|
||||||
|
const char hwstr[] = "Hello World!!!";
|
||||||
|
LOG(INFO) << "Print: '" << hwstr << "' via puts()";
|
||||||
|
sapi::v::Array<const char> hwarr(hwstr, sizeof(hwstr));
|
||||||
|
sapi::v::Int ret;
|
||||||
|
SAPI_RETURN_IF_ERROR(GetSandbox()->Call("puts", &ret, hwarr.PtrBefore()));
|
||||||
|
TRANSACTION_FAIL_IF_NOT(ret.GetValue() == 15, "puts('Hello World!!!') != 15");
|
||||||
|
|
||||||
|
sapi::v::Int vp;
|
||||||
|
sapi::v::NullPtr nptr;
|
||||||
|
LOG(INFO) << "Test whether pointer is NOT NULL - new pointers";
|
||||||
|
SAPI_RETURN_IF_ERROR(f.testptr(vp.PtrBefore()));
|
||||||
|
LOG(INFO) << "Test whether pointer is NULL";
|
||||||
|
SAPI_RETURN_IF_ERROR(f.testptr(&nptr));
|
||||||
|
|
||||||
|
// Protobuf test.
|
||||||
|
sumsapi::SumParamsProto proto;
|
||||||
|
proto.set_a(10);
|
||||||
|
proto.set_b(20);
|
||||||
|
proto.set_c(30);
|
||||||
|
sapi::v::Proto<sumsapi::SumParamsProto> pp(proto);
|
||||||
|
SAPI_ASSIGN_OR_RETURN(v, f.sumproto(pp.PtrBefore()));
|
||||||
|
LOG(INFO) << "sumproto(proto {a = 10; b = 20; c = 30}) = " << v;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(v == 60,
|
||||||
|
"sumproto(proto {a = 10; b = 20; c = 30}) != 60");
|
||||||
|
|
||||||
|
// Fd transfer test.
|
||||||
|
int fdesc = open("/proc/self/exe", O_RDONLY);
|
||||||
|
sapi::v::Fd fd(fdesc);
|
||||||
|
SAPI_RETURN_IF_ERROR(GetSandbox()->TransferToSandboxee(&fd));
|
||||||
|
LOG(INFO) << "remote_fd = " << fd.GetRemoteFd();
|
||||||
|
TRANSACTION_FAIL_IF_NOT(fd.GetRemoteFd() == 3, "remote_fd != 3");
|
||||||
|
|
||||||
|
fdesc = open("/proc/self/comm", O_RDONLY);
|
||||||
|
sapi::v::Fd fd2(fdesc);
|
||||||
|
SAPI_RETURN_IF_ERROR(GetSandbox()->TransferToSandboxee(&fd2));
|
||||||
|
LOG(INFO) << "remote_fd2 = " << fd2.GetRemoteFd();
|
||||||
|
TRANSACTION_FAIL_IF_NOT(fd2.GetRemoteFd() == 4, "remote_fd2 != 4");
|
||||||
|
|
||||||
|
// Read from fd test.
|
||||||
|
char buffer[1024] = {0};
|
||||||
|
sapi::v::Array<char> buf(buffer, sizeof(buffer));
|
||||||
|
sapi::v::UInt size(128);
|
||||||
|
SAPI_RETURN_IF_ERROR(GetSandbox()->Call("read", &ret, &fd2, buf.PtrBoth(), &size));
|
||||||
|
LOG(INFO) << "Read from /proc/self/comm = [" << buffer << "]";
|
||||||
|
|
||||||
|
// Close test.
|
||||||
|
SAPI_RETURN_IF_ERROR(fd2.CloseRemoteFd(GetSandbox()->GetRpcChannel()));
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
SAPI_RETURN_IF_ERROR(GetSandbox()->Call("read", &ret, &fd2, buf.PtrBoth(), &size));
|
||||||
|
LOG(INFO) << "Read from closed /proc/self/comm = [" << buffer << "]";
|
||||||
|
|
||||||
|
// Pass fd as function arg example.
|
||||||
|
fdesc = open("/proc/self/statm", O_RDONLY);
|
||||||
|
sapi::v::Fd fd3(fdesc);
|
||||||
|
SAPI_RETURN_IF_ERROR(GetSandbox()->TransferToSandboxee(&fd3));
|
||||||
|
SAPI_ASSIGN_OR_RETURN(int r2, f.read_int(fd3.GetRemoteFd()));
|
||||||
|
LOG(INFO) << "statm value (should not be 0) = " << r2;
|
||||||
|
|
||||||
|
if (crash_) {
|
||||||
|
// Crashes the sandboxed part with SIGSEGV
|
||||||
|
LOG(INFO) << "Crash with SIGSEGV";
|
||||||
|
SAPI_RETURN_IF_ERROR(f.crash());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (violate_) {
|
||||||
|
LOG(INFO) << "Cause a sandbox (syscall) violation";
|
||||||
|
SAPI_RETURN_IF_ERROR(f.violate());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time_out_) {
|
||||||
|
SAPI_RETURN_IF_ERROR(f.sleep_for_sec(kTimeOutVal * 2));
|
||||||
|
}
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status test_addition(sapi::Sandbox* sandbox, int a, int b, int c) {
|
||||||
|
SumApi f(sandbox);
|
||||||
|
|
||||||
|
SAPI_ASSIGN_OR_RETURN(int v, f.sum(a, b));
|
||||||
|
TRANSACTION_FAIL_IF_NOT(v == c, absl::StrCat(a, " + ", b, " != ", c));
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
sapi::Status status;
|
||||||
|
|
||||||
|
sapi::BasicTransaction st{absl::make_unique<SumSapiSandbox>()};
|
||||||
|
// Using the simple transaction (and function pointers):
|
||||||
|
CHECK(st.Run(test_addition, 1, 1, 2).ok());
|
||||||
|
CHECK(st.Run(test_addition, 1336, 1, 1337).ok());
|
||||||
|
CHECK(st.Run(test_addition, 1336, 1, 7).code() ==
|
||||||
|
sapi::StatusCode::kFailedPrecondition);
|
||||||
|
|
||||||
|
status = st.Run([](sapi::Sandbox* sandbox) -> sapi::Status {
|
||||||
|
SumApi f(sandbox);
|
||||||
|
|
||||||
|
// Sums two int's held in a structure.
|
||||||
|
SumParams params;
|
||||||
|
params.mutable_data()->a = 1111;
|
||||||
|
params.mutable_data()->b = 222;
|
||||||
|
params.mutable_data()->ret = 0;
|
||||||
|
SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
|
||||||
|
LOG(INFO) << "1111 + 222 = " << params.data().ret;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(params.data().ret == 1333, "1111 + 222 != 1333");
|
||||||
|
return sapi::OkStatus();
|
||||||
|
});
|
||||||
|
CHECK(status.ok()) << status.message();
|
||||||
|
|
||||||
|
status = st.Run([](sapi::Sandbox* sandbox) -> sapi::Status {
|
||||||
|
SumApi f(sandbox);
|
||||||
|
SumParams params;
|
||||||
|
params.mutable_data()->a = 1111;
|
||||||
|
params.mutable_data()->b = -1000;
|
||||||
|
params.mutable_data()->ret = 0;
|
||||||
|
SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
|
||||||
|
LOG(INFO) << "1111 - 1000 = " << params.data().ret;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(params.data().ret == 111, "1111 - 1000 != 111");
|
||||||
|
|
||||||
|
// Without the wrapper class for struct.
|
||||||
|
sapi::v::Struct<sum_params> p;
|
||||||
|
p.mutable_data()->a = 1234;
|
||||||
|
p.mutable_data()->b = 5678;
|
||||||
|
p.mutable_data()->ret = 0;
|
||||||
|
SAPI_RETURN_IF_ERROR(f.sums(p.PtrBoth()));
|
||||||
|
LOG(INFO) << "1234 + 5678 = " << p.data().ret;
|
||||||
|
TRANSACTION_FAIL_IF_NOT(p.data().ret == 6912, "1234 + 5678 != 6912");
|
||||||
|
return sapi::OkStatus();
|
||||||
|
});
|
||||||
|
CHECK(status.ok()) << status.message();
|
||||||
|
|
||||||
|
// Using overloaded transaction class:
|
||||||
|
SumTransaction sapi_crash{absl::make_unique<SumSapiSandbox>(), /*crash=*/true,
|
||||||
|
/*violate=*/false,
|
||||||
|
/*time_out=*/false};
|
||||||
|
status = sapi_crash.Run();
|
||||||
|
LOG(INFO) << "Final run result for crash: " << status;
|
||||||
|
CHECK(status.code() == sapi::StatusCode::kUnavailable);
|
||||||
|
|
||||||
|
SumTransaction sapi_violate{absl::make_unique<SumSapiSandbox>(),
|
||||||
|
/*crash=*/false,
|
||||||
|
/*violate=*/true,
|
||||||
|
/*time_out=*/false};
|
||||||
|
status = sapi_violate.Run();
|
||||||
|
LOG(INFO) << "Final run result for violate: " << status;
|
||||||
|
CHECK(status.code() == sapi::StatusCode::kUnavailable);
|
||||||
|
|
||||||
|
SumTransaction sapi_timeout{absl::make_unique<SumSapiSandbox>(),
|
||||||
|
/*crash=*/false,
|
||||||
|
/*violate=*/false,
|
||||||
|
/*time_out=*/true};
|
||||||
|
status = sapi_timeout.Run();
|
||||||
|
LOG(INFO) << "Final run result for timeout: " << status;
|
||||||
|
CHECK(status.code() == sapi::StatusCode::kUnavailable);
|
||||||
|
|
||||||
|
SumTransaction sapi{absl::make_unique<SumSapiSandbox>(), /*crash=*/false,
|
||||||
|
/*violate=*/false, /*time_out=*/false};
|
||||||
|
for (int i = 0; i < 32; ++i) {
|
||||||
|
status = sapi.Run();
|
||||||
|
LOG(INFO) << "Final run result for not a crash: " << status.message();
|
||||||
|
CHECK(status.ok());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
48
sandboxed_api/examples/zlib/BUILD.bazel
Normal file
48
sandboxed_api/examples/zlib/BUILD.bazel
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Description: Sandboxed API reimplementation of zlib's zpipe.c example.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
load("//sandboxed_api/bazel:sapi.bzl", "sapi_library")
|
||||||
|
|
||||||
|
sapi_library(
|
||||||
|
name = "zlib-sapi",
|
||||||
|
srcs = [],
|
||||||
|
hdrs = [],
|
||||||
|
embed = True,
|
||||||
|
functions = [
|
||||||
|
"deflateInit_",
|
||||||
|
"deflate",
|
||||||
|
"deflateEnd",
|
||||||
|
],
|
||||||
|
lib = "@net_zlib//:zlib",
|
||||||
|
lib_name = "Zlib",
|
||||||
|
namespace = "sapi::zlib",
|
||||||
|
deps = [],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_binary(
|
||||||
|
name = "main_zlib",
|
||||||
|
srcs = ["main_zlib.cc"],
|
||||||
|
copts = ["-Wframe-larger-than=65536"],
|
||||||
|
deps = [
|
||||||
|
":zlib-sapi",
|
||||||
|
":zlib-sapi_embed",
|
||||||
|
"//sandboxed_api:vars",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
],
|
||||||
|
)
|
124
sandboxed_api/examples/zlib/main_zlib.cc
Normal file
124
sandboxed_api/examples/zlib/main_zlib.cc
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <linux/audit.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "absl/base/macros.h"
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "sandboxed_api/examples/zlib/zlib-sapi.sapi.h"
|
||||||
|
#include "sandboxed_api/examples/zlib/zlib-sapi_embed.h"
|
||||||
|
#include "sandboxed_api/vars.h"
|
||||||
|
|
||||||
|
// Need to define these manually, as zlib.h cannot be directly included. The
|
||||||
|
// interface generator makes all functions available that were requested in
|
||||||
|
// sapi_library(), but does not know which macro constants are needed by the
|
||||||
|
// sandboxee.
|
||||||
|
#define Z_NO_FLUSH 0
|
||||||
|
#define Z_FINISH 4
|
||||||
|
#define Z_OK 0
|
||||||
|
#define Z_DEFAULT_COMPRESSION (-1)
|
||||||
|
#define Z_ERRNO (-1)
|
||||||
|
#define Z_STREAM_ERROR (-2)
|
||||||
|
#define Z_STREAM_END 1
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
sapi::Sandbox sandbox(sapi::zlib::zlib_sapi_embed_create());
|
||||||
|
sapi::zlib::ZlibApi api(&sandbox);
|
||||||
|
if (!sandbox.Init().ok()) {
|
||||||
|
LOG(FATAL) << "Couldn't initialize the Sandboxed API Lib";
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret, flush;
|
||||||
|
unsigned have;
|
||||||
|
sapi::v::Struct<sapi::zlib::z_stream> strm;
|
||||||
|
|
||||||
|
constexpr int kChunk = 16384;
|
||||||
|
unsigned char in_[kChunk] = {0};
|
||||||
|
unsigned char out_[kChunk] = {0};
|
||||||
|
sapi::v::Array<unsigned char> input(in_, kChunk);
|
||||||
|
sapi::v::Array<unsigned char> output(out_, kChunk);
|
||||||
|
if (!sandbox.Allocate(&input).ok() || !sandbox.Allocate(&output).ok()) {
|
||||||
|
LOG(FATAL) << "Allocate memory failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr char kZlibVersion[] = "1.2.11";
|
||||||
|
sapi::v::Array<const char> version(kZlibVersion,
|
||||||
|
ABSL_ARRAYSIZE(kZlibVersion));
|
||||||
|
|
||||||
|
// Allocate deflate state.
|
||||||
|
*strm.mutable_data() = sapi::zlib::z_stream{};
|
||||||
|
|
||||||
|
ret = api.deflateInit_(strm.PtrBoth(), Z_DEFAULT_COMPRESSION,
|
||||||
|
version.PtrBefore(), sizeof(sapi::zlib::z_stream))
|
||||||
|
.ValueOrDie();
|
||||||
|
if (ret != Z_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << "Starting decompression";
|
||||||
|
|
||||||
|
// Compress until end of file.
|
||||||
|
do {
|
||||||
|
strm.mutable_data()->avail_in = fread(input.GetLocal(), 1, kChunk, stdin);
|
||||||
|
if (!sandbox.TransferToSandboxee(&input).ok()) {
|
||||||
|
LOG(FATAL) << "TransferToSandboxee failed";
|
||||||
|
}
|
||||||
|
if (ferror(stdin)) {
|
||||||
|
LOG(INFO) << "Error reading from stdin";
|
||||||
|
(void)api.deflateEnd(strm.PtrBoth()).ValueOrDie();
|
||||||
|
return Z_ERRNO;
|
||||||
|
}
|
||||||
|
flush = feof(stdin) ? Z_FINISH : Z_NO_FLUSH;
|
||||||
|
strm.mutable_data()->next_in =
|
||||||
|
reinterpret_cast<unsigned char*>(input.GetRemote());
|
||||||
|
|
||||||
|
// Run deflate() on input until output buffer not full, finish compression
|
||||||
|
// if all of source has been read in.
|
||||||
|
do {
|
||||||
|
strm.mutable_data()->avail_out = kChunk;
|
||||||
|
strm.mutable_data()->next_out =
|
||||||
|
reinterpret_cast<unsigned char*>(output.GetRemote());
|
||||||
|
|
||||||
|
ret = api.deflate(strm.PtrBoth(), flush)
|
||||||
|
.ValueOrDie(); // no bad return value.
|
||||||
|
|
||||||
|
assert(ret != Z_STREAM_ERROR); // state not clobbered.
|
||||||
|
have = kChunk - strm.data().avail_out;
|
||||||
|
|
||||||
|
if (!sandbox.TransferFromSandboxee(&output).ok()) {
|
||||||
|
LOG(FATAL) << "TransferFromSandboxee failed";
|
||||||
|
}
|
||||||
|
if (fwrite(output.GetLocal(), 1, have, stdout) != have ||
|
||||||
|
ferror(stdout)) {
|
||||||
|
// Not really necessary as strm did not change from last transfer.
|
||||||
|
(void)api.deflateEnd(strm.PtrBoth()).ValueOrDie();
|
||||||
|
return Z_ERRNO;
|
||||||
|
}
|
||||||
|
} while (strm.data().avail_out == 0);
|
||||||
|
assert(strm.data().avail_in == 0); // all input will be used.
|
||||||
|
|
||||||
|
// done when last data in file processed.
|
||||||
|
} while (flush != Z_FINISH);
|
||||||
|
assert(ret == Z_STREAM_END); // stream will be complete.
|
||||||
|
|
||||||
|
// clean up and return.
|
||||||
|
(void)api.deflateEnd(strm.PtrBoth()).ValueOrDie();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
32
sandboxed_api/file_toc.h
Normal file
32
sandboxed_api/file_toc.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// This file defines the structure of the table-of-contents elements
|
||||||
|
// produced by sapi_cc_embed_data rules.
|
||||||
|
// Its canonical definition is here, but a copy of it appears in each file
|
||||||
|
// generated by //sandboxed_api/bazel:filewrapper.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_FILE_TOC_H_
|
||||||
|
#define SANDBOXED_API_FILE_TOC_H_
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
struct FileToc {
|
||||||
|
const char* name;
|
||||||
|
const char* data;
|
||||||
|
size_t size;
|
||||||
|
unsigned char md5digest[16]; // Not used, kept for compatibility
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_FILE_TOC_H_
|
35
sandboxed_api/lenval_core.h
Normal file
35
sandboxed_api/lenval_core.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Contains the inner structure used by var_lenval. It will be allocated in the
|
||||||
|
// sandboxee's memory and is used for sharing buffers with varying sizes.
|
||||||
|
#ifndef SANDBOXED_API_LENVAL_CORE_H_
|
||||||
|
#define SANDBOXED_API_LENVAL_CORE_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
|
||||||
|
struct LenValStruct {
|
||||||
|
LenValStruct(uint64_t size, void* data) : size(size), data(data) {}
|
||||||
|
|
||||||
|
LenValStruct() : LenValStruct(0, nullptr) {}
|
||||||
|
|
||||||
|
uint64_t size;
|
||||||
|
void* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sapi
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_LENVAL_CORE_H_
|
24
sandboxed_api/proto_arg.proto
Normal file
24
sandboxed_api/proto_arg.proto
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package sapi;
|
||||||
|
|
||||||
|
message ProtoArg {
|
||||||
|
// Fully qualified name of the protobuf that is being passed.
|
||||||
|
required string full_name = 1;
|
||||||
|
// The serialized protobuf data.
|
||||||
|
required bytes protobuf_data = 2;
|
||||||
|
}
|
59
sandboxed_api/proto_helper.h
Normal file
59
sandboxed_api/proto_helper.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Utility functions for protobuf handling.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_PROTO_HELPER_H_
|
||||||
|
#define SANDBOXED_API_PROTO_HELPER_H_
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/proto_arg.pb.h"
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::vector<uint8_t> SerializeProto(const T& proto) {
|
||||||
|
// Wrap protobuf in a envelope so that we know the name of the protobuf
|
||||||
|
// structure when deserializing in the sandboxee.
|
||||||
|
ProtoArg proto_arg;
|
||||||
|
proto_arg.set_protobuf_data(proto.SerializeAsString());
|
||||||
|
proto_arg.set_full_name(proto.GetDescriptor()->full_name());
|
||||||
|
std::vector<uint8_t> serialized_proto(proto_arg.ByteSizeLong());
|
||||||
|
|
||||||
|
if (!proto_arg.SerializeToArray(serialized_proto.data(),
|
||||||
|
serialized_proto.size())) {
|
||||||
|
LOG(ERROR) << "Unable to serialize array";
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialized_proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool DeserializeProto(T* result, const char* data, size_t len) {
|
||||||
|
ProtoArg envelope;
|
||||||
|
if (!envelope.ParseFromArray(data, len)) {
|
||||||
|
LOG(ERROR) << "Unable to deserialize envelope";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pb_data = envelope.protobuf_data();
|
||||||
|
return result->ParseFromArray(pb_data.c_str(), pb_data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sapi
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_PROTO_HELPER_H_
|
207
sandboxed_api/rpcchannel.cc
Normal file
207
sandboxed_api/rpcchannel.cc
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sandboxed_api/rpcchannel.h"
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/synchronization/mutex.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/util/canonical_errors.h"
|
||||||
|
#include "sandboxed_api/util/status_macros.h"
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::Call(const FuncCall& call, uint32_t tag, FuncRet* ret,
|
||||||
|
v::Type exp_type) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
if (!comms_->SendTLV(tag, sizeof(call),
|
||||||
|
reinterpret_cast<const uint8_t*>(&call))) {
|
||||||
|
return sapi::UnavailableError("Sending TLV value failed");
|
||||||
|
}
|
||||||
|
SAPI_ASSIGN_OR_RETURN(auto fret, Return(exp_type));
|
||||||
|
*ret = fret;
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::StatusOr<FuncRet> RPCChannel::Return(v::Type exp_type) {
|
||||||
|
uint32_t tag;
|
||||||
|
uint64_t len;
|
||||||
|
FuncRet ret;
|
||||||
|
if (!comms_->RecvTLV(&tag, &len, &ret, sizeof(ret))) {
|
||||||
|
return sapi::UnavailableError("Receiving TLV value failed");
|
||||||
|
}
|
||||||
|
if (tag != comms::kMsgReturn) {
|
||||||
|
LOG(ERROR) << "tag != comms::kMsgReturn (" << absl::StrCat(absl::Hex(tag))
|
||||||
|
<< " != " << absl::StrCat(absl::Hex(comms::kMsgReturn)) << ")";
|
||||||
|
return sapi::UnavailableError("Received TLV has incorrect tag");
|
||||||
|
}
|
||||||
|
if (len != sizeof(FuncRet)) {
|
||||||
|
LOG(ERROR) << "len != sizeof(FuncReturn) (" << len
|
||||||
|
<< " != " << sizeof(FuncRet) << ")";
|
||||||
|
return sapi::UnavailableError("Received TLV has incorrect length");
|
||||||
|
}
|
||||||
|
if (ret.ret_type != exp_type) {
|
||||||
|
LOG(ERROR) << "FuncRet->type != exp_type (" << ret.ret_type
|
||||||
|
<< " != " << exp_type << ")";
|
||||||
|
return sapi::UnavailableError("Received TLV has incorrect return type");
|
||||||
|
}
|
||||||
|
if (!ret.success) {
|
||||||
|
LOG(ERROR) << "FuncRet->success == false";
|
||||||
|
return sapi::UnavailableError("Function call failed");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::Allocate(size_t size, void** addr) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
uint64_t sz = size;
|
||||||
|
if (!comms_->SendTLV(comms::kMsgAllocate, sizeof(sz),
|
||||||
|
reinterpret_cast<uint8_t*>(&sz))) {
|
||||||
|
return sapi::UnavailableError("Sending TLV value failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_ASSIGN_OR_RETURN(auto fret, Return(v::Type::kPointer));
|
||||||
|
*addr = reinterpret_cast<void*>(fret.int_val);
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::Reallocate(void* old_addr, size_t size,
|
||||||
|
void** new_addr) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
comms::ReallocRequest req;
|
||||||
|
req.old_addr = reinterpret_cast<uint64_t>(old_addr);
|
||||||
|
req.size = size;
|
||||||
|
|
||||||
|
if (!comms_->SendTLV(comms::kMsgReallocate, sizeof(comms::ReallocRequest),
|
||||||
|
reinterpret_cast<uint8_t*>(&req))) {
|
||||||
|
return sapi::UnavailableError("Sending TLV value failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fret_or = Return(v::Type::kPointer);
|
||||||
|
if (!fret_or.ok()) {
|
||||||
|
*new_addr = nullptr;
|
||||||
|
return sapi::UnavailableError(
|
||||||
|
absl::StrCat("Reallocate() failed on the remote side: ",
|
||||||
|
fret_or.status().message()));
|
||||||
|
}
|
||||||
|
auto fret = std::move(fret_or).ValueOrDie();
|
||||||
|
|
||||||
|
*new_addr = reinterpret_cast<void*>(fret.int_val);
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::Free(void* addr) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
uint64_t remote = reinterpret_cast<uint64_t>(addr);
|
||||||
|
if (!comms_->SendTLV(comms::kMsgFree, sizeof(remote),
|
||||||
|
reinterpret_cast<uint8_t*>(&remote))) {
|
||||||
|
return sapi::UnavailableError("Sending TLV value failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_ASSIGN_OR_RETURN(auto fret, Return(v::Type::kVoid));
|
||||||
|
if (!fret.success) {
|
||||||
|
return sapi::UnavailableError("Free() failed on the remote side");
|
||||||
|
}
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::Symbol(const char* symname, void** addr) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
if (!comms_->SendTLV(comms::kMsgSymbol, strlen(symname) + 1,
|
||||||
|
reinterpret_cast<const uint8_t*>(symname))) {
|
||||||
|
return sapi::UnavailableError("Sending TLV value failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_ASSIGN_OR_RETURN(auto fret, Return(v::Type::kPointer));
|
||||||
|
*addr = reinterpret_cast<void*>(fret.int_val);
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::Exit() {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
if (comms_->IsTerminated()) {
|
||||||
|
VLOG(2) << "Comms channel already terminated";
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the RPC exit sequence. But, the only thing that matters as a success
|
||||||
|
// indicator is whether the Comms channel had been closed
|
||||||
|
bool unused = true;
|
||||||
|
comms_->SendTLV(comms::kMsgExit, sizeof(unused),
|
||||||
|
reinterpret_cast<uint8_t*>(&unused));
|
||||||
|
comms_->RecvBool(&unused);
|
||||||
|
|
||||||
|
if (!comms_->IsTerminated()) {
|
||||||
|
LOG(ERROR) << "Comms channel not terminated in Exit()";
|
||||||
|
// TODO(hamacher): Better error code
|
||||||
|
return sapi::FailedPreconditionError(
|
||||||
|
"Comms channel not terminated in Exit()");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::SendFD(int local_fd, int* remote_fd) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
bool unused = true;
|
||||||
|
if (!comms_->SendTLV(comms::kMsgSendFd, sizeof(unused),
|
||||||
|
reinterpret_cast<uint8_t*>(&unused))) {
|
||||||
|
return sapi::UnavailableError("Sending TLV value failed");
|
||||||
|
}
|
||||||
|
if (!comms_->SendFD(local_fd)) {
|
||||||
|
return sapi::UnavailableError("Sending FD failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_ASSIGN_OR_RETURN(auto fret, Return(v::Type::kInt));
|
||||||
|
if (!fret.success) {
|
||||||
|
return sapi::UnavailableError("SendFD failed on the remote side");
|
||||||
|
}
|
||||||
|
*remote_fd = fret.int_val;
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::RecvFD(int remote_fd, int* local_fd) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
if (!comms_->SendTLV(comms::kMsgRecvFd, sizeof(remote_fd),
|
||||||
|
reinterpret_cast<uint8_t*>(&remote_fd))) {
|
||||||
|
return sapi::UnavailableError("Sending TLV value failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!comms_->RecvFD(local_fd)) {
|
||||||
|
return sapi::UnavailableError("Receving FD failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_ASSIGN_OR_RETURN(auto fret, Return(v::Type::kVoid));
|
||||||
|
if (!fret.success) {
|
||||||
|
return sapi::UnavailableError("RecvFD failed on the remote side");
|
||||||
|
}
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sapi::Status RPCChannel::Close(int remote_fd) {
|
||||||
|
absl::MutexLock lock(&mutex_);
|
||||||
|
if (!comms_->SendTLV(comms::kMsgClose, sizeof(remote_fd),
|
||||||
|
reinterpret_cast<uint8_t*>(&remote_fd))) {
|
||||||
|
return sapi::UnavailableError("Sending TLV value failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_ASSIGN_OR_RETURN(auto fret, Return(v::Type::kVoid));
|
||||||
|
if (!fret.success) {
|
||||||
|
return sapi::UnavailableError("Close() failed on the remote side");
|
||||||
|
}
|
||||||
|
return sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sapi
|
75
sandboxed_api/rpcchannel.h
Normal file
75
sandboxed_api/rpcchannel.h
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_RPCCHANNEL_H_
|
||||||
|
#define SANDBOXED_API_RPCCHANNEL_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "absl/synchronization/mutex.h"
|
||||||
|
#include "sandboxed_api/call.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/util/status.h"
|
||||||
|
#include "sandboxed_api/util/statusor.h"
|
||||||
|
#include "sandboxed_api/var_type.h"
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
|
||||||
|
// This class exposes functions which provide primitives operating over the
|
||||||
|
// Comms channel.
|
||||||
|
class RPCChannel {
|
||||||
|
public:
|
||||||
|
explicit RPCChannel(sandbox2::Comms* comms) : comms_(comms) {}
|
||||||
|
|
||||||
|
// Calls a function.
|
||||||
|
sapi::Status Call(const FuncCall& call, uint32_t tag, FuncRet* ret,
|
||||||
|
v::Type exp_type);
|
||||||
|
|
||||||
|
// Allocates memory.
|
||||||
|
sapi::Status Allocate(size_t size, void** addr);
|
||||||
|
|
||||||
|
// Reallocates memory.
|
||||||
|
sapi::Status Reallocate(void* old_addr, size_t size, void** new_addr);
|
||||||
|
|
||||||
|
// Frees memory.
|
||||||
|
sapi::Status Free(void* addr);
|
||||||
|
|
||||||
|
// Returns address of a symbol.
|
||||||
|
sapi::Status Symbol(const char* symname, void** addr);
|
||||||
|
|
||||||
|
// Makes the remote part exit.
|
||||||
|
sapi::Status Exit();
|
||||||
|
|
||||||
|
// Transfers fd to sandboxee.
|
||||||
|
sapi::Status SendFD(int local_fd, int* remote_fd);
|
||||||
|
|
||||||
|
// Retrieves fd from sandboxee.
|
||||||
|
sapi::Status RecvFD(int remote_fd, int* local_fd);
|
||||||
|
|
||||||
|
// Closes fd in sandboxee.
|
||||||
|
sapi::Status Close(int remote_fd);
|
||||||
|
|
||||||
|
sandbox2::Comms* comms() const { return comms_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Receives the result after a call.
|
||||||
|
sapi::StatusOr<FuncRet> Return(v::Type exp_type);
|
||||||
|
|
||||||
|
sandbox2::Comms* comms_; // Owned by sandbox2;
|
||||||
|
absl::Mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sapi
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_RPCCHANNEL_H_
|
421
sandboxed_api/sandbox.cc
Normal file
421
sandboxed_api/sandbox.cc
Normal file
|
@ -0,0 +1,421 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox.h"
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "absl/base/casts.h"
|
||||||
|
#include "absl/base/macros.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/time/time.h"
|
||||||
|
#include "sandboxed_api/embed_file.h"
|
||||||
|
#include "sandboxed_api/rpcchannel.h"
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/fileops.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/path.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/runfiles.h"
|
||||||
|
#include "sandboxed_api/util/canonical_errors.h"
|
||||||
|
#include "sandboxed_api/util/status_macros.h"
|
||||||
|
|
||||||
|
namespace file = ::sandbox2::file;
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
|
||||||
|
Sandbox::~Sandbox() {
|
||||||
|
Terminate();
|
||||||
|
// The forkserver will die automatically when the executor goes out of scope
|
||||||
|
// and closes the comms object.
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic policy which should work with majority of typical libraries, which
|
||||||
|
// are single-threaded and require ~30 basic syscalls.
|
||||||
|
void InitDefaultPolicyBuilder(sandbox2::PolicyBuilder* builder) {
|
||||||
|
(*builder)
|
||||||
|
.EnableNamespaces()
|
||||||
|
.AllowRead()
|
||||||
|
.AllowWrite()
|
||||||
|
.AllowExit()
|
||||||
|
.AllowGetRlimit()
|
||||||
|
.AllowGetIDs()
|
||||||
|
.AllowTCGETS()
|
||||||
|
.AllowTime()
|
||||||
|
.AllowOpen()
|
||||||
|
.AllowStat()
|
||||||
|
.AllowHandleSignals()
|
||||||
|
.AllowSystemMalloc()
|
||||||
|
.AllowSafeFcntl()
|
||||||
|
.AllowSyscalls({
|
||||||
|
__NR_recvmsg,
|
||||||
|
__NR_sendmsg,
|
||||||
|
__NR_futex,
|
||||||
|
__NR_close,
|
||||||
|
__NR_lseek,
|
||||||
|
__NR_getpid,
|
||||||
|
__NR_getppid,
|
||||||
|
__NR_gettid,
|
||||||
|
__NR_clock_nanosleep,
|
||||||
|
__NR_nanosleep,
|
||||||
|
__NR_uname,
|
||||||
|
__NR_getrandom,
|
||||||
|
__NR_kill,
|
||||||
|
__NR_tgkill,
|
||||||
|
__NR_tkill,
|
||||||
|
__NR_readlink,
|
||||||
|
#ifdef __NR_arch_prctl // x86-64 only
|
||||||
|
__NR_arch_prctl,
|
||||||
|
#endif
|
||||||
|
})
|
||||||
|
.AddFile("/etc/localtime")
|
||||||
|
.AddTmpfs("/tmp", 1ULL << 30 /* 1GiB tmpfs (max size) */);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sandbox::Terminate(bool attempt_graceful_exit) {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt_graceful_exit) {
|
||||||
|
// Gracefully ask it to exit (with 1 second limit) first, then kill it.
|
||||||
|
Exit();
|
||||||
|
} else {
|
||||||
|
// Kill it straight away
|
||||||
|
s2_->Kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& result = AwaitResult();
|
||||||
|
if (result.final_status() == sandbox2::Result::OK &&
|
||||||
|
result.reason_code() == 0) {
|
||||||
|
VLOG(2) << "Sandbox2 finished with: " << result.ToString();
|
||||||
|
} else {
|
||||||
|
LOG(WARNING) << "Sandbox2 finished with: " << result.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string PathToSAPILib(const std::string& lib_path) {
|
||||||
|
return file::IsAbsolutePath(lib_path)
|
||||||
|
? lib_path
|
||||||
|
: sandbox2::GetDataDependencyFilePath(lib_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::Init() {
|
||||||
|
// It's already initialized
|
||||||
|
if (IsActive()) {
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the forkserver if it is not already running.
|
||||||
|
if (!fork_client_) {
|
||||||
|
// If FileToc was specified, it will be used over any paths to the SAPI
|
||||||
|
// library.
|
||||||
|
std::string lib_path;
|
||||||
|
int embed_lib_fd = -1;
|
||||||
|
if (embed_lib_toc_) {
|
||||||
|
embed_lib_fd = EmbedFile::GetEmbedFileSingleton()->GetDupFdForFileToc(
|
||||||
|
embed_lib_toc_);
|
||||||
|
if (embed_lib_fd == -1) {
|
||||||
|
PLOG(ERROR) << "Cannot create executable FD for TOC:'"
|
||||||
|
<< embed_lib_toc_->name << "'";
|
||||||
|
return ::sapi::UnavailableError("Could not create executable FD");
|
||||||
|
}
|
||||||
|
lib_path = embed_lib_toc_->name;
|
||||||
|
} else {
|
||||||
|
lib_path = PathToSAPILib(GetLibPath());
|
||||||
|
if (lib_path.empty()) {
|
||||||
|
LOG(ERROR) << "SAPI library path is empty";
|
||||||
|
return ::sapi::FailedPreconditionError("No SAPI library path given");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> args{lib_path};
|
||||||
|
// Additional arguments, if needed.
|
||||||
|
GetArgs(&args);
|
||||||
|
std::vector<std::string> envs{};
|
||||||
|
// Additional envvars, if needed.
|
||||||
|
GetEnvs(&envs);
|
||||||
|
|
||||||
|
forkserver_executor_ =
|
||||||
|
(embed_lib_fd >= 0)
|
||||||
|
? absl::make_unique<sandbox2::Executor>(embed_lib_fd, args, envs)
|
||||||
|
: absl::make_unique<sandbox2::Executor>(lib_path, args, envs);
|
||||||
|
|
||||||
|
fork_client_ = forkserver_executor_->StartForkServer();
|
||||||
|
|
||||||
|
if (!fork_client_) {
|
||||||
|
LOG(ERROR) << "Could not start forkserver";
|
||||||
|
return ::sapi::UnavailableError("Could not start the forkserver");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sandbox2::PolicyBuilder policy_builder;
|
||||||
|
InitDefaultPolicyBuilder(&policy_builder);
|
||||||
|
auto s2p = ModifyPolicy(&policy_builder);
|
||||||
|
|
||||||
|
// Spawn new process from the forkserver.
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(fork_client_.get());
|
||||||
|
|
||||||
|
executor
|
||||||
|
// The client.cc code is capable of enabling sandboxing on its own.
|
||||||
|
->set_enable_sandbox_before_exec(false)
|
||||||
|
// By default, set cwd to "/", can be changed in ModifyExecutor().
|
||||||
|
.set_cwd("/")
|
||||||
|
.limits()
|
||||||
|
// Disable time limits.
|
||||||
|
->set_walltime_limit(absl::ZeroDuration())
|
||||||
|
.set_rlimit_cpu(RLIM64_INFINITY)
|
||||||
|
// Needed by the Scudo Allocator, and by various *SAN options.
|
||||||
|
.set_rlimit_as(RLIM64_INFINITY);
|
||||||
|
|
||||||
|
// Modify the executor, e.g. by setting custom limits and IPC.
|
||||||
|
ModifyExecutor(executor.get());
|
||||||
|
|
||||||
|
s2_ = absl::make_unique<sandbox2::Sandbox2>(std::move(executor),
|
||||||
|
std::move(s2p));
|
||||||
|
auto res = s2_->RunAsync();
|
||||||
|
|
||||||
|
comms_ = s2_->comms();
|
||||||
|
pid_ = s2_->GetPid();
|
||||||
|
|
||||||
|
rpc_channel_ = absl::make_unique<RPCChannel>(comms_);
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
Terminate();
|
||||||
|
return ::sapi::UnavailableError("Could not start the sandbox");
|
||||||
|
}
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sandbox::IsActive() const { return s2_ && !s2_->IsTerminated(); }
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::Allocate(v::Var* var, bool automatic_free) {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
return var->Allocate(GetRpcChannel(), automatic_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::Free(v::Var* var) {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
return var->Free(GetRpcChannel());
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::SynchronizePtrBefore(v::Callable* ptr) {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
if (ptr->GetType() != v::Type::kPointer) {
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
// Cast is safe, since type is v::Type::kPointer
|
||||||
|
auto* p = static_cast<v::Ptr*>(ptr);
|
||||||
|
if (p->GetSyncType() == v::Pointable::SYNC_NONE) {
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p->GetPointedVar()->GetRemote() == nullptr) {
|
||||||
|
// Allocate the memory, and make it automatically free-able, upon this
|
||||||
|
// object's (p->GetPointedVar()) end of life-time.
|
||||||
|
SAPI_RETURN_IF_ERROR(Allocate(p->GetPointedVar(), /*automatic_free=*/true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocation occurs during both before/after synchronization modes. But the
|
||||||
|
// memory is transferred to the sandboxee only if v::Pointable::SYNC_BEFORE
|
||||||
|
// was requested.
|
||||||
|
if ((p->GetSyncType() & v::Pointable::SYNC_BEFORE) == 0) {
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
VLOG(3) << "Synchronization (TO), ptr " << p << ", Type: " << p->GetSyncType()
|
||||||
|
<< " for var: " << p->GetPointedVar()->ToString();
|
||||||
|
|
||||||
|
return p->GetPointedVar()->TransferToSandboxee(GetRpcChannel(), GetPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::SynchronizePtrAfter(v::Callable* ptr) const {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
if (ptr->GetType() != v::Type::kPointer) {
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
v::Ptr* p = reinterpret_cast<v::Ptr*>(ptr);
|
||||||
|
if ((p->GetSyncType() & v::Pointable::SYNC_AFTER) == 0) {
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
VLOG(3) << "Synchronization (FROM), ptr " << p
|
||||||
|
<< ", Type: " << p->GetSyncType()
|
||||||
|
<< " for var: " << p->GetPointedVar()->ToString();
|
||||||
|
|
||||||
|
if (p->GetPointedVar()->GetRemote() == nullptr) {
|
||||||
|
LOG(ERROR) << "Trying to synchronize a variable which is not allocated in "
|
||||||
|
<< "the sandboxee p=" << p->ToString();
|
||||||
|
return ::sapi::FailedPreconditionError(absl::StrCat(
|
||||||
|
"Trying to synchronize a variable which is not allocated in the "
|
||||||
|
"sandboxee p=",
|
||||||
|
p->ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return p->GetPointedVar()->TransferFromSandboxee(GetRpcChannel(), GetPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::Call(const std::string& func, v::Callable* ret,
|
||||||
|
std::initializer_list<v::Callable*> args) {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
// Send data.
|
||||||
|
FuncCall rfcall{};
|
||||||
|
rfcall.argc = args.size();
|
||||||
|
absl::SNPrintF(rfcall.func, ABSL_ARRAYSIZE(rfcall.func), "%s", func);
|
||||||
|
|
||||||
|
VLOG(1) << "CALL ENTRY: '" << func << "' with " << args.size()
|
||||||
|
<< " argument(s)";
|
||||||
|
|
||||||
|
// Copy all arguments into rfcall.
|
||||||
|
int i = 0;
|
||||||
|
for (auto* arg : args) {
|
||||||
|
rfcall.arg_size[i] = arg->GetSize();
|
||||||
|
rfcall.arg_type[i] = arg->GetType();
|
||||||
|
|
||||||
|
// For pointers, set the auxiliary type and size.
|
||||||
|
if (rfcall.arg_type[i] == v::Type::kPointer) {
|
||||||
|
// Cast is safe, since type is v::Type::kPointer
|
||||||
|
auto* p = static_cast<v::Ptr*>(arg);
|
||||||
|
rfcall.aux_type[i] = p->GetPointedVar()->GetType();
|
||||||
|
rfcall.aux_size[i] = p->GetPointedVar()->GetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize all pointers before the call if it's needed.
|
||||||
|
SAPI_RETURN_IF_ERROR(SynchronizePtrBefore(arg));
|
||||||
|
|
||||||
|
if (arg->GetType() == v::Type::kFloat) {
|
||||||
|
arg->GetDataFromPtr(&rfcall.args[i].arg_float,
|
||||||
|
sizeof(rfcall.args[0].arg_float));
|
||||||
|
} else {
|
||||||
|
arg->GetDataFromPtr(&rfcall.args[i].arg_int,
|
||||||
|
sizeof(rfcall.args[0].arg_int));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rfcall.arg_type[i] == v::Type::kFd) {
|
||||||
|
// Cast is safe, since type is v::Type::kFd
|
||||||
|
auto* fd = static_cast<v::Fd*>(arg);
|
||||||
|
if (fd->GetRemoteFd() < 0) {
|
||||||
|
SAPI_RETURN_IF_ERROR(TransferToSandboxee(fd));
|
||||||
|
}
|
||||||
|
rfcall.args[i].arg_int = fd->GetRemoteFd();
|
||||||
|
}
|
||||||
|
|
||||||
|
VLOG(1) << "CALL ARG: (" << i << "), Type: " << arg->GetTypeString()
|
||||||
|
<< ", Size: " << arg->GetSize() << ", Val: " << arg->ToString();
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
rfcall.ret_type = ret->GetType();
|
||||||
|
rfcall.ret_size = ret->GetSize();
|
||||||
|
|
||||||
|
// Call & receive data.
|
||||||
|
FuncRet fret;
|
||||||
|
SAPI_RETURN_IF_ERROR(
|
||||||
|
GetRpcChannel()->Call(rfcall, comms::kMsgCall, &fret, rfcall.ret_type));
|
||||||
|
|
||||||
|
if (fret.ret_type == v::Type::kFloat) {
|
||||||
|
ret->SetDataFromPtr(&fret.float_val, sizeof(fret.float_val));
|
||||||
|
} else {
|
||||||
|
ret->SetDataFromPtr(&fret.int_val, sizeof(fret.int_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fret.ret_type == v::Type::kFd) {
|
||||||
|
SAPI_RETURN_IF_ERROR(TransferFromSandboxee(reinterpret_cast<v::Fd*>(ret)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize all pointers after the call if it's needed.
|
||||||
|
for (auto* arg : args) {
|
||||||
|
SAPI_RETURN_IF_ERROR(SynchronizePtrAfter(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
VLOG(1) << "CALL EXIT: Type: " << ret->GetTypeString()
|
||||||
|
<< ", Size: " << ret->GetSize() << ", Val: " << ret->ToString();
|
||||||
|
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::Symbol(const char* symname, void** addr) {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
return rpc_channel_->Symbol(symname, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::TransferToSandboxee(v::Var* var) {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
return var->TransferToSandboxee(GetRpcChannel(), GetPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::TransferFromSandboxee(v::Var* var) {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
return var->TransferFromSandboxee(GetRpcChannel(), GetPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
const sandbox2::Result& Sandbox::AwaitResult() {
|
||||||
|
if (s2_) {
|
||||||
|
result_ = s2_->AwaitResult();
|
||||||
|
s2_.reset(nullptr);
|
||||||
|
}
|
||||||
|
return result_;
|
||||||
|
}
|
||||||
|
|
||||||
|
::sapi::Status Sandbox::SetWallTimeLimit(time_t limit) const {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return ::sapi::UnavailableError("Sandbox not active");
|
||||||
|
}
|
||||||
|
s2_->SetWallTimeLimit(limit);
|
||||||
|
return ::sapi::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sandbox::Exit() const {
|
||||||
|
if (!IsActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Give it 1 second
|
||||||
|
s2_->SetWallTimeLimit(1);
|
||||||
|
if (!rpc_channel_->Exit().ok()) {
|
||||||
|
LOG(WARNING) << "rpc_channel->Exit() failed, killing PID: " << GetPid();
|
||||||
|
s2_->Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<sandbox2::Policy> Sandbox::ModifyPolicy(
|
||||||
|
sandbox2::PolicyBuilder* builder) {
|
||||||
|
return builder->BuildOrDie();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sapi
|
155
sandboxed_api/sandbox.h
Normal file
155
sandboxed_api/sandbox.h
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "sandboxed_api/file_toc.h"
|
||||||
|
#include "absl/base/macros.h"
|
||||||
|
#include "sandboxed_api/rpcchannel.h"
|
||||||
|
#include "sandboxed_api/sandbox2/client.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
#include "sandboxed_api/vars.h"
|
||||||
|
|
||||||
|
namespace sapi {
|
||||||
|
|
||||||
|
// The Sandbox class represents the sandboxed library. It provides users with
|
||||||
|
// means to communicate with it (make function calls, transfer memory).
|
||||||
|
class Sandbox {
|
||||||
|
public:
|
||||||
|
Sandbox(const Sandbox&) = delete;
|
||||||
|
Sandbox& operator=(const Sandbox&) = delete;
|
||||||
|
|
||||||
|
explicit Sandbox(const FileToc* embed_lib_toc)
|
||||||
|
: comms_(nullptr), pid_(0), embed_lib_toc_(embed_lib_toc) {}
|
||||||
|
virtual ~Sandbox();
|
||||||
|
|
||||||
|
// Initializes a new sandboxing session.
|
||||||
|
::sapi::Status Init();
|
||||||
|
|
||||||
|
// Is the current sandboxing session alive?
|
||||||
|
bool IsActive() const;
|
||||||
|
|
||||||
|
// Terminates the current sandboxing session (if it exists).
|
||||||
|
void Terminate(bool attempt_graceful_exit = true);
|
||||||
|
|
||||||
|
// Restarts the sandbox.
|
||||||
|
::sapi::Status Restart(bool attempt_graceful_exit) {
|
||||||
|
Terminate(attempt_graceful_exit);
|
||||||
|
return Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters for common fields.
|
||||||
|
sandbox2::Comms* comms() const { return comms_; }
|
||||||
|
|
||||||
|
RPCChannel* GetRpcChannel() const { return rpc_channel_.get(); }
|
||||||
|
|
||||||
|
int GetPid() const { return pid_; }
|
||||||
|
|
||||||
|
// Synchronizes the underlying memory for the pointer before the call.
|
||||||
|
::sapi::Status SynchronizePtrBefore(v::Callable* ptr);
|
||||||
|
|
||||||
|
// Synchronizes the underlying memory for pointer after the call.
|
||||||
|
::sapi::Status SynchronizePtrAfter(v::Callable* ptr) const;
|
||||||
|
|
||||||
|
// Makes a call to the sandboxee.
|
||||||
|
template <typename... Args>
|
||||||
|
::sapi::Status Call(const std::string& func, v::Callable* ret, Args&&... args) {
|
||||||
|
static_assert(sizeof...(Args) <= FuncCall::kArgsMax,
|
||||||
|
"Too many arguments to sapi::Sandbox::Call()");
|
||||||
|
return Call(func, ret, {std::forward<Args>(args)...});
|
||||||
|
}
|
||||||
|
::sapi::Status Call(const std::string& func, v::Callable* ret,
|
||||||
|
std::initializer_list<v::Callable*> args);
|
||||||
|
|
||||||
|
// Allocates memory in the sandboxee, automatic_free indicates whether the
|
||||||
|
// memory should be freed on the remote side when the 'var' goes out of scope.
|
||||||
|
::sapi::Status Allocate(v::Var* var, bool automatic_free = false);
|
||||||
|
|
||||||
|
// Frees memory in the sandboxee.
|
||||||
|
::sapi::Status Free(v::Var* var);
|
||||||
|
|
||||||
|
// Finds address of a symbol in the sandboxee.
|
||||||
|
::sapi::Status Symbol(const char* symname, void** addr);
|
||||||
|
|
||||||
|
// Transfers memory (both directions). Status is returned (memory transfer
|
||||||
|
// succeeded/failed).
|
||||||
|
::sapi::Status TransferToSandboxee(v::Var* var);
|
||||||
|
::sapi::Status TransferFromSandboxee(v::Var* var);
|
||||||
|
|
||||||
|
// Waits until the sandbox terminated and returns the result.
|
||||||
|
const sandbox2::Result& AwaitResult();
|
||||||
|
const sandbox2::Result& result() const { return result_; }
|
||||||
|
|
||||||
|
::sapi::Status SetWallTimeLimit(time_t limit) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// Gets the arguments passed to the sandboxee.
|
||||||
|
virtual void GetArgs(std::vector<std::string>* args) const {
|
||||||
|
args->push_back("--logtostderr=true");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Returns the sandbox policy. Subclasses can modify the default policy
|
||||||
|
// builder, or return a completely new policy.
|
||||||
|
virtual std::unique_ptr<sandbox2::Policy> ModifyPolicy(
|
||||||
|
sandbox2::PolicyBuilder* builder);
|
||||||
|
|
||||||
|
// Path of the sandboxee:
|
||||||
|
// - relative to runfiles directory: ::sandbox2::GetDataDependencyFilePath()
|
||||||
|
// will be applied to it,
|
||||||
|
// - absolute: will be used as is.
|
||||||
|
virtual std::string GetLibPath() const { return ""; }
|
||||||
|
|
||||||
|
// Gets the environment varialbes passed to the sandboxee.
|
||||||
|
virtual void GetEnvs(std::vector<std::string>* envs) const {}
|
||||||
|
|
||||||
|
// Modifies the Executor object if needed.
|
||||||
|
virtual void ModifyExecutor(sandbox2::Executor* executor) {}
|
||||||
|
|
||||||
|
// Exits the sandboxee.
|
||||||
|
void Exit() const;
|
||||||
|
|
||||||
|
// The client to the library forkserver.
|
||||||
|
std::unique_ptr<sandbox2::ForkClient> fork_client_;
|
||||||
|
std::unique_ptr<sandbox2::Executor> forkserver_executor_;
|
||||||
|
|
||||||
|
// The main sandbox2::Sandbox2 object.
|
||||||
|
std::unique_ptr<sandbox2::Sandbox2> s2_;
|
||||||
|
|
||||||
|
// Result of the most recent sandbox execution
|
||||||
|
sandbox2::Result result_;
|
||||||
|
|
||||||
|
// Comms with the sandboxee.
|
||||||
|
sandbox2::Comms* comms_;
|
||||||
|
// RPCChannel object.
|
||||||
|
std::unique_ptr<RPCChannel> rpc_channel_;
|
||||||
|
// The main pid of the sandboxee.
|
||||||
|
pid_t pid_;
|
||||||
|
|
||||||
|
// FileTOC with the embedded library, takes precedence over GetLibPath if
|
||||||
|
// present (not nullptr).
|
||||||
|
const FileToc* embed_lib_toc_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sapi
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX_H_
|
734
sandboxed_api/sandbox2/BUILD.bazel
Normal file
734
sandboxed_api/sandbox2/BUILD.bazel
Normal file
|
@ -0,0 +1,734 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Description: sandbox2 is a C++ sandbox technology for Linux.
|
||||||
|
|
||||||
|
package(
|
||||||
|
default_visibility = [
|
||||||
|
"//sandboxed_api:__subpackages__",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
load("//sandboxed_api/bazel:proto.bzl", "sapi_proto_library")
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "bpfdisassembler",
|
||||||
|
srcs = ["bpfdisassembler.cc"],
|
||||||
|
hdrs = ["bpfdisassembler.h"],
|
||||||
|
deps = [
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "regs",
|
||||||
|
srcs = ["regs.cc"],
|
||||||
|
hdrs = ["regs.h"],
|
||||||
|
deps = [
|
||||||
|
":deathrattle_fatalmsg_proto_cc",
|
||||||
|
":syscall",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "syscall",
|
||||||
|
srcs = [
|
||||||
|
"syscall.cc",
|
||||||
|
"syscall_defs.cc",
|
||||||
|
"syscall_defs.h",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"syscall.h",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":util",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
"@com_google_absl//absl/types:span",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "syscall_test",
|
||||||
|
srcs = ["syscall_test.cc"],
|
||||||
|
deps = [
|
||||||
|
":syscall",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "result",
|
||||||
|
srcs = ["result.cc"],
|
||||||
|
hdrs = ["result.h"],
|
||||||
|
deps = [
|
||||||
|
":regs",
|
||||||
|
":syscall",
|
||||||
|
":util",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"@com_google_absl//absl/base",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_proto_library(
|
||||||
|
name = "logserver_proto",
|
||||||
|
srcs = ["logserver.proto"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "logserver",
|
||||||
|
srcs = ["logserver.cc"],
|
||||||
|
hdrs = ["logserver.h"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":logserver_proto_cc",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "logsink",
|
||||||
|
srcs = ["logsink.cc"],
|
||||||
|
hdrs = ["logsink.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":logserver_proto_cc",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/synchronization",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "network_proxy_server",
|
||||||
|
srcs = ["network_proxy_server.cc"],
|
||||||
|
hdrs = ["network_proxy_server.h"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "network_proxy_client",
|
||||||
|
srcs = ["network_proxy_client.cc"],
|
||||||
|
hdrs = ["network_proxy_client.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/synchronization",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "ipc",
|
||||||
|
srcs = ["ipc.cc"],
|
||||||
|
hdrs = ["ipc.h"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":logserver",
|
||||||
|
":logsink",
|
||||||
|
":network_proxy_client",
|
||||||
|
":network_proxy_server",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "policy",
|
||||||
|
srcs = ["policy.cc"],
|
||||||
|
hdrs = ["policy.h"],
|
||||||
|
deps = [
|
||||||
|
":bpfdisassembler",
|
||||||
|
":comms",
|
||||||
|
":deathrattle_fatalmsg_proto_cc",
|
||||||
|
":namespace",
|
||||||
|
":regs",
|
||||||
|
":syscall",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/types:optional",
|
||||||
|
"@org_kernel_libcap//:libcap",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "notify",
|
||||||
|
srcs = [],
|
||||||
|
hdrs = ["notify.h"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":result",
|
||||||
|
":syscall",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "limits",
|
||||||
|
hdrs = ["limits.h"],
|
||||||
|
deps = [
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/time",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "global_forkserver",
|
||||||
|
srcs = ["global_forkclient.cc"],
|
||||||
|
hdrs = ["global_forkclient.h"],
|
||||||
|
deps = [
|
||||||
|
":client",
|
||||||
|
":comms",
|
||||||
|
":forkserver",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should not be used in sandboxee code if it only uses sandbox2::Comms and
|
||||||
|
# sandbox2::Client objects
|
||||||
|
cc_library(
|
||||||
|
name = "sandbox2",
|
||||||
|
srcs = [
|
||||||
|
"executor.cc",
|
||||||
|
"monitor.cc",
|
||||||
|
"monitor.h",
|
||||||
|
"policybuilder.cc",
|
||||||
|
"sandbox2.cc",
|
||||||
|
"stack-trace.cc",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"executor.h",
|
||||||
|
"policybuilder.h",
|
||||||
|
"sandbox2.h",
|
||||||
|
"stack-trace.h",
|
||||||
|
# Workaround: reexporting the header files of our dependencies, see b/15420638
|
||||||
|
"ipc.h",
|
||||||
|
"limits.h",
|
||||||
|
"notify.h",
|
||||||
|
"policy.h",
|
||||||
|
"regs.h",
|
||||||
|
"result.h",
|
||||||
|
"syscall.h",
|
||||||
|
"client.h",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":client",
|
||||||
|
":comms",
|
||||||
|
":deathrattle_fatalmsg_proto_cc",
|
||||||
|
":forkserver",
|
||||||
|
":forkserver_proto_cc",
|
||||||
|
":global_forkserver",
|
||||||
|
":ipc",
|
||||||
|
":limits",
|
||||||
|
":logsink",
|
||||||
|
":mounts",
|
||||||
|
":namespace",
|
||||||
|
":notify",
|
||||||
|
":policy",
|
||||||
|
":regs",
|
||||||
|
":result",
|
||||||
|
":syscall",
|
||||||
|
":util",
|
||||||
|
":network_proxy_client",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/container:flat_hash_map",
|
||||||
|
"@com_google_absl//absl/container:flat_hash_set",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
"@com_google_absl//absl/synchronization",
|
||||||
|
"@com_google_absl//absl/time",
|
||||||
|
"@com_google_absl//absl/types:optional",
|
||||||
|
"@org_kernel_libcap//:libcap",
|
||||||
|
"//sandboxed_api/sandbox2/unwind",
|
||||||
|
"//sandboxed_api/sandbox2/unwind:unwind_proto_cc",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/sandbox2/util:file_base",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:statusor",
|
||||||
|
|
||||||
|
# Do not remove this dependency as it defines ptrace_wrapped.
|
||||||
|
"//sandboxed_api/sandbox2/unwind:ptrace_hook", # buildcleaner: keep
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should be used in sandboxee code instead of :sandbox2 if it uses just
|
||||||
|
# sandbox2::Client::SandboxMeHere() and sandbox2::Comms
|
||||||
|
cc_library(
|
||||||
|
name = "client",
|
||||||
|
srcs = [
|
||||||
|
"client.cc",
|
||||||
|
"sanitizer.cc",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"client.h",
|
||||||
|
"sanitizer.h",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":logsink",
|
||||||
|
":network_proxy_client",
|
||||||
|
"//sandboxed_api/sandbox2/util:file_helpers",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "forkserver",
|
||||||
|
srcs = ["forkserver.cc"],
|
||||||
|
hdrs = ["forkserver.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":client",
|
||||||
|
":comms",
|
||||||
|
":forkserver_proto_cc",
|
||||||
|
":namespace",
|
||||||
|
":policy",
|
||||||
|
":syscall",
|
||||||
|
":util",
|
||||||
|
"//sandboxed_api/sandbox2/unwind",
|
||||||
|
"//sandboxed_api/sandbox2/unwind:ptrace_hook",
|
||||||
|
"//sandboxed_api/sandbox2/unwind:unwind_proto_cc",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
"@com_google_absl//absl/synchronization",
|
||||||
|
"@org_kernel_libcap//:libcap",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "mounts",
|
||||||
|
srcs = ["mounts.cc"],
|
||||||
|
hdrs = ["mounts.h"],
|
||||||
|
deps = [
|
||||||
|
":mounttree_proto_cc",
|
||||||
|
"//sandboxed_api/sandbox2/util:file_base",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/sandbox2/util:minielf",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:statusor",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/container:flat_hash_set",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
"@com_google_protobuf//:protobuf",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "mounts_test",
|
||||||
|
srcs = ["mounts_test.cc"],
|
||||||
|
data = ["//sandboxed_api/sandbox2/testcases:minimal_dynamic"],
|
||||||
|
deps = [
|
||||||
|
":mounts",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:file_base",
|
||||||
|
"//sandboxed_api/sandbox2/util:temp_file",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "namespace",
|
||||||
|
srcs = ["namespace.cc"],
|
||||||
|
hdrs = ["namespace.h"],
|
||||||
|
deps = [
|
||||||
|
":deathrattle_fatalmsg_proto_cc",
|
||||||
|
":mounts",
|
||||||
|
":mounttree_proto_cc",
|
||||||
|
":util",
|
||||||
|
"//sandboxed_api/sandbox2/util:file_base",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "namespace_test",
|
||||||
|
srcs = ["namespace_test.cc"],
|
||||||
|
data = [
|
||||||
|
"//sandboxed_api/sandbox2/testcases:hostname",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:namespace",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":namespace",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "forkingclient",
|
||||||
|
srcs = ["forkingclient.cc"],
|
||||||
|
hdrs = ["forkingclient.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":client",
|
||||||
|
":comms",
|
||||||
|
":forkserver",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "util",
|
||||||
|
srcs = ["util.cc"],
|
||||||
|
hdrs = ["util.h"],
|
||||||
|
# The default is 16384, however we need to do a clone with a
|
||||||
|
# stack-allocated buffer -- and PTHREAD_STACK_MIN also happens to be 16384.
|
||||||
|
# Thus the slight increase.
|
||||||
|
copts = ["-Wframe-larger-than=17000"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2/util:file_base",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:statusor",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "buffer",
|
||||||
|
srcs = ["buffer.cc"],
|
||||||
|
hdrs = ["buffer.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":util",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:statusor",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "buffer_test",
|
||||||
|
srcs = ["buffer_test.cc"],
|
||||||
|
data = ["//sandboxed_api/sandbox2/testcases:buffer"],
|
||||||
|
deps = [
|
||||||
|
":buffer",
|
||||||
|
":comms",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_proto_library(
|
||||||
|
name = "forkserver_proto",
|
||||||
|
srcs = ["forkserver.proto"],
|
||||||
|
deps = [":mounttree_proto"],
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_proto_library(
|
||||||
|
name = "mounttree_proto",
|
||||||
|
srcs = ["mounttree.proto"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "comms",
|
||||||
|
srcs = ["comms.cc"],
|
||||||
|
hdrs = ["comms.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":util",
|
||||||
|
"//sandboxed_api/sandbox2/util:strerror",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:statusor",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
"@com_google_absl//absl/synchronization",
|
||||||
|
"@com_google_protobuf//:protobuf",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_proto_library(
|
||||||
|
name = "comms_test_proto",
|
||||||
|
srcs = ["comms_test.proto"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "comms_test",
|
||||||
|
srcs = ["comms_test.cc"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":comms_test_proto_cc",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/container:fixed_array",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
"@com_google_protobuf//:protobuf",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "forkserver_test",
|
||||||
|
srcs = [
|
||||||
|
"forkserver_test.cc",
|
||||||
|
"global_forkclient.h",
|
||||||
|
],
|
||||||
|
data = ["//sandboxed_api/sandbox2/testcases:minimal"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":forkserver",
|
||||||
|
":forkserver_proto_cc",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "limits_test",
|
||||||
|
srcs = ["limits_test.cc"],
|
||||||
|
data = ["//sandboxed_api/sandbox2/testcases:limits"],
|
||||||
|
deps = [
|
||||||
|
":limits",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "notify_test",
|
||||||
|
srcs = ["notify_test.cc"],
|
||||||
|
data = [
|
||||||
|
"//sandboxed_api/sandbox2/testcases:personality",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:pidcomms",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":regs",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "policy_test",
|
||||||
|
srcs = ["policy_test.cc"],
|
||||||
|
data = [
|
||||||
|
"//sandboxed_api/sandbox2/testcases:add_policy_on_syscalls",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:malloc_system",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:minimal",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:minimal_dynamic",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:policy",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
":limits",
|
||||||
|
":regs",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "sandbox2_test",
|
||||||
|
srcs = ["sandbox2_test.cc"],
|
||||||
|
data = [
|
||||||
|
"//sandboxed_api/sandbox2/testcases:abort",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:minimal",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:sleep",
|
||||||
|
"//sandboxed_api/sandbox2/testcases:tsync",
|
||||||
|
],
|
||||||
|
tags = ["local"],
|
||||||
|
deps = [
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "sanitizer_test",
|
||||||
|
srcs = ["sanitizer_test.cc"],
|
||||||
|
data = ["//sandboxed_api/sandbox2/testcases:sanitizer"],
|
||||||
|
deps = [
|
||||||
|
":client",
|
||||||
|
":comms",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
":util",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "util_test",
|
||||||
|
srcs = ["util_test.cc"],
|
||||||
|
deps = [
|
||||||
|
":testing",
|
||||||
|
":util",
|
||||||
|
"//sandboxed_api/sandbox2/util:file_base",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "stack-trace_test",
|
||||||
|
srcs = ["stack-trace_test.cc"],
|
||||||
|
data = ["//sandboxed_api/sandbox2/testcases:symbolize"],
|
||||||
|
deps = [
|
||||||
|
":global_forkserver",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/sandbox2/util:fileops",
|
||||||
|
"//sandboxed_api/sandbox2/util:temp_file",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "ipc_test",
|
||||||
|
srcs = ["ipc_test.cc"],
|
||||||
|
data = ["//sandboxed_api/sandbox2/testcases:ipc"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Utility library for writing tests
|
||||||
|
cc_library(
|
||||||
|
name = "testing",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = ["testing.cc"],
|
||||||
|
hdrs = ["testing.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2/util:file_base",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
sapi_proto_library(
|
||||||
|
name = "deathrattle_fatalmsg_proto",
|
||||||
|
srcs = ["deathrattle_fatalmsg.proto"],
|
||||||
|
deps = [":mounttree_proto"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "policybuilder_test",
|
||||||
|
srcs = ["policybuilder_test.cc"],
|
||||||
|
data = ["//sandboxed_api/sandbox2/testcases:print_fds"],
|
||||||
|
deps = [
|
||||||
|
":comms",
|
||||||
|
":sandbox2",
|
||||||
|
":testing",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/util:status",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
19
sandboxed_api/sandbox2/README.md
Normal file
19
sandboxed_api/sandbox2/README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Sandbox2
|
||||||
|
|
||||||
|
Sandbox2 is a C++ security sandbox for Linux which can be used to run untrusted
|
||||||
|
programs or portions of programs in confined environments. The idea is that the
|
||||||
|
runtime environment is so restricted that security bugs such as buffer overflows
|
||||||
|
in the protected region cause no harm.
|
||||||
|
|
||||||
|
## Who is it for?
|
||||||
|
|
||||||
|
Sandbox2 is aimed to sandbox C/C++ code or whole binaries in production.
|
||||||
|
|
||||||
|
See the sandboxing options [overview page](../docs/sandbox-overview.md) to make
|
||||||
|
sure this is the type of sandboxing you are looking for.
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
Read our [How it works](docs/howitworks.md) page to learn everything about this
|
||||||
|
technology.
|
||||||
|
|
231
sandboxed_api/sandbox2/bpfdisassembler.cc
Normal file
231
sandboxed_api/sandbox2/bpfdisassembler.cc
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/bpfdisassembler.h"
|
||||||
|
|
||||||
|
// IWYU pragma: no_include <asm/int-ll64.h>
|
||||||
|
#include <linux/filter.h>
|
||||||
|
#include <linux/seccomp.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
|
||||||
|
#define INSIDE_FIELD(what, field) \
|
||||||
|
((offsetof(seccomp_data, field) == 0 || \
|
||||||
|
(what) >= offsetof(seccomp_data, field)) && \
|
||||||
|
((what) < (offsetof(seccomp_data, field) + sizeof(seccomp_data::field))))
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
namespace bpf {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string OperandToString(int op) {
|
||||||
|
switch (op) {
|
||||||
|
case BPF_ADD:
|
||||||
|
return "+";
|
||||||
|
case BPF_SUB:
|
||||||
|
return "-";
|
||||||
|
case BPF_MUL:
|
||||||
|
return "*";
|
||||||
|
case BPF_DIV:
|
||||||
|
return "/";
|
||||||
|
case BPF_XOR:
|
||||||
|
return "^";
|
||||||
|
case BPF_AND:
|
||||||
|
return "&";
|
||||||
|
case BPF_OR:
|
||||||
|
return "|";
|
||||||
|
case BPF_RSH:
|
||||||
|
return ">>";
|
||||||
|
case BPF_LSH:
|
||||||
|
return "<<";
|
||||||
|
default:
|
||||||
|
return absl::StrCat("[unknown op ", op, "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ComparisonToString(int op) {
|
||||||
|
switch (op) {
|
||||||
|
case BPF_JGE:
|
||||||
|
return ">=";
|
||||||
|
case BPF_JGT:
|
||||||
|
return ">";
|
||||||
|
case BPF_JEQ:
|
||||||
|
return "==";
|
||||||
|
case BPF_JSET:
|
||||||
|
return "&";
|
||||||
|
default:
|
||||||
|
return absl::StrCat("[unknown cmp ", op, "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NegatedComparisonToString(int op) {
|
||||||
|
switch (op) {
|
||||||
|
case BPF_JGE:
|
||||||
|
return "<";
|
||||||
|
case BPF_JGT:
|
||||||
|
return "<=";
|
||||||
|
case BPF_JEQ:
|
||||||
|
return "!=";
|
||||||
|
default:
|
||||||
|
return absl::StrCat("[unknown neg cmp ", op, "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::string DecodeInstruction(const sock_filter& inst, int pc) {
|
||||||
|
constexpr auto kArgSize = sizeof(seccomp_data::args[0]);
|
||||||
|
const int op = BPF_OP(inst.code);
|
||||||
|
const int true_target = inst.jt + pc + 1;
|
||||||
|
const int false_target = inst.jf + pc + 1;
|
||||||
|
switch (inst.code) {
|
||||||
|
case BPF_LD | BPF_W | BPF_ABS:
|
||||||
|
if (inst.k & 3) {
|
||||||
|
return absl::StrCat("A := *0x", absl::Hex(inst.k),
|
||||||
|
" (misaligned read)");
|
||||||
|
}
|
||||||
|
if (INSIDE_FIELD(inst.k, nr)) {
|
||||||
|
return "A := syscall number";
|
||||||
|
}
|
||||||
|
if (INSIDE_FIELD(inst.k, arch)) {
|
||||||
|
return "A := architecture";
|
||||||
|
}
|
||||||
|
if (INSIDE_FIELD(inst.k, instruction_pointer)) {
|
||||||
|
// TODO(swiecki) handle big-endian.
|
||||||
|
if (inst.k != offsetof(seccomp_data, instruction_pointer)) {
|
||||||
|
return "A := instruction pointer high";
|
||||||
|
}
|
||||||
|
return "A := instruction pointer low";
|
||||||
|
}
|
||||||
|
if (INSIDE_FIELD(inst.k, args)) {
|
||||||
|
const int argno = (inst.k - offsetof(seccomp_data, args)) / kArgSize;
|
||||||
|
// TODO(swiecki) handle big-endian.
|
||||||
|
if (inst.k != (offsetof(seccomp_data, args) + argno * kArgSize)) {
|
||||||
|
return absl::StrCat("A := arg ", argno, " high");
|
||||||
|
}
|
||||||
|
return absl::StrCat("A := arg ", argno, " low");
|
||||||
|
}
|
||||||
|
return absl::StrCat("A := data[0x", absl::Hex(inst.k),
|
||||||
|
"] (invalid load)");
|
||||||
|
case BPF_LD | BPF_W | BPF_LEN:
|
||||||
|
return "A := sizeof(seccomp_data)";
|
||||||
|
case BPF_LDX | BPF_W | BPF_LEN:
|
||||||
|
return "X := sizeof(seccomp_data)";
|
||||||
|
case BPF_LD | BPF_IMM:
|
||||||
|
return absl::StrCat("A := 0x", absl::Hex(inst.k));
|
||||||
|
case BPF_LDX | BPF_IMM:
|
||||||
|
return absl::StrCat("X := 0x", absl::Hex(inst.k));
|
||||||
|
case BPF_MISC | BPF_TAX:
|
||||||
|
return "X := A";
|
||||||
|
case BPF_MISC | BPF_TXA:
|
||||||
|
return "A := X";
|
||||||
|
case BPF_LD | BPF_MEM:
|
||||||
|
return absl::StrCat("A := M[", inst.k, "]");
|
||||||
|
case BPF_LDX | BPF_MEM:
|
||||||
|
return absl::StrCat("X := M[", inst.k, "]");
|
||||||
|
case BPF_ST:
|
||||||
|
return absl::StrCat("M[", inst.k, "] := A");
|
||||||
|
case BPF_STX:
|
||||||
|
return absl::StrCat("M[", inst.k, "] := X");
|
||||||
|
case BPF_RET | BPF_K: {
|
||||||
|
__u32 data = inst.k & SECCOMP_RET_DATA;
|
||||||
|
switch (inst.k & SECCOMP_RET_ACTION) {
|
||||||
|
case SECCOMP_RET_KILL:
|
||||||
|
return "KILL";
|
||||||
|
case SECCOMP_RET_ALLOW:
|
||||||
|
return "ALLOW";
|
||||||
|
case SECCOMP_RET_TRAP:
|
||||||
|
return absl::StrCat("TRAP 0x", absl::Hex(data));
|
||||||
|
case SECCOMP_RET_ERRNO:
|
||||||
|
return absl::StrCat("ERRNO 0x", absl::Hex(data));
|
||||||
|
case SECCOMP_RET_TRACE:
|
||||||
|
return absl::StrCat("TRACE 0x", absl::Hex(data));
|
||||||
|
default:
|
||||||
|
return absl::StrCat("return 0x", absl::Hex(inst.k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case BPF_RET | BPF_A:
|
||||||
|
return "return A";
|
||||||
|
case BPF_ALU | BPF_ADD | BPF_K:
|
||||||
|
case BPF_ALU | BPF_SUB | BPF_K:
|
||||||
|
case BPF_ALU | BPF_MUL | BPF_K:
|
||||||
|
case BPF_ALU | BPF_DIV | BPF_K:
|
||||||
|
case BPF_ALU | BPF_AND | BPF_K:
|
||||||
|
case BPF_ALU | BPF_OR | BPF_K:
|
||||||
|
case BPF_ALU | BPF_XOR | BPF_K:
|
||||||
|
case BPF_ALU | BPF_LSH | BPF_K:
|
||||||
|
case BPF_ALU | BPF_RSH | BPF_K:
|
||||||
|
return absl::StrCat("A := A ", OperandToString(op), " 0x",
|
||||||
|
absl::Hex(inst.k));
|
||||||
|
case BPF_ALU | BPF_ADD | BPF_X:
|
||||||
|
case BPF_ALU | BPF_SUB | BPF_X:
|
||||||
|
case BPF_ALU | BPF_MUL | BPF_X:
|
||||||
|
case BPF_ALU | BPF_DIV | BPF_X:
|
||||||
|
case BPF_ALU | BPF_AND | BPF_X:
|
||||||
|
case BPF_ALU | BPF_OR | BPF_X:
|
||||||
|
case BPF_ALU | BPF_XOR | BPF_X:
|
||||||
|
case BPF_ALU | BPF_LSH | BPF_X:
|
||||||
|
case BPF_ALU | BPF_RSH | BPF_X:
|
||||||
|
return absl::StrCat("A := A ", OperandToString(op), " X");
|
||||||
|
case BPF_ALU | BPF_NEG:
|
||||||
|
return "A := -A";
|
||||||
|
case BPF_JMP | BPF_JA:
|
||||||
|
return absl::StrCat("jump to ", inst.k + pc + 1);
|
||||||
|
case BPF_JMP | BPF_JEQ | BPF_K:
|
||||||
|
case BPF_JMP | BPF_JGE | BPF_K:
|
||||||
|
case BPF_JMP | BPF_JGT | BPF_K:
|
||||||
|
case BPF_JMP | BPF_JSET | BPF_K:
|
||||||
|
if (inst.jf == 0) {
|
||||||
|
return absl::StrCat("if A ", ComparisonToString(op), " 0x",
|
||||||
|
absl::Hex(inst.k), " goto ", true_target);
|
||||||
|
}
|
||||||
|
if (inst.jt == 0 && op != BPF_JSET) {
|
||||||
|
return absl::StrCat("if A ", NegatedComparisonToString(op), " 0x",
|
||||||
|
absl::Hex(inst.k), " goto ", false_target);
|
||||||
|
}
|
||||||
|
return absl::StrCat("if A ", ComparisonToString(op), " 0x",
|
||||||
|
absl::Hex(inst.k), " then ", true_target, " else ",
|
||||||
|
false_target);
|
||||||
|
case BPF_JMP | BPF_JEQ | BPF_X:
|
||||||
|
case BPF_JMP | BPF_JGE | BPF_X:
|
||||||
|
case BPF_JMP | BPF_JGT | BPF_X:
|
||||||
|
case BPF_JMP | BPF_JSET | BPF_X:
|
||||||
|
if (inst.jf == 0) {
|
||||||
|
return absl::StrCat("if A ", ComparisonToString(op), " X goto ",
|
||||||
|
true_target);
|
||||||
|
}
|
||||||
|
if (inst.jt == 0 && op != BPF_JSET) {
|
||||||
|
return absl::StrCat("if A ", NegatedComparisonToString(op), " X goto ",
|
||||||
|
false_target);
|
||||||
|
}
|
||||||
|
return absl::StrCat("if A ", ComparisonToString(op), " X then ",
|
||||||
|
true_target, " else ", false_target);
|
||||||
|
default:
|
||||||
|
return absl::StrCat("Invalid instruction ", inst.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Disasm(const std::vector<sock_filter>& prog) {
|
||||||
|
std::string rv;
|
||||||
|
for (size_t i = 0, len = prog.size(); i < len; ++i) {
|
||||||
|
absl::StrAppend(&rv, absl::Dec(i, absl::kZeroPad3), ": ",
|
||||||
|
DecodeInstruction(prog[i], i), "\n");
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace bpf
|
||||||
|
} // namespace sandbox2
|
36
sandboxed_api/sandbox2/bpfdisassembler.h
Normal file
36
sandboxed_api/sandbox2/bpfdisassembler.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX2_BPFDISASSEMBLER_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX2_BPFDISASSEMBLER_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct sock_filter;
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
namespace bpf {
|
||||||
|
|
||||||
|
// Decodes a BPF instruction into textual representation.
|
||||||
|
std::string DecodeInstruction(const sock_filter& inst, int pc);
|
||||||
|
|
||||||
|
// Disassembles a BPF program.
|
||||||
|
// Returns a human-readable textual represenation.
|
||||||
|
std::string Disasm(const std::vector<sock_filter>& prog);
|
||||||
|
|
||||||
|
} // namespace bpf
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX2_BPFDISASSEMBLER_H_
|
78
sandboxed_api/sandbox2/buffer.cc
Normal file
78
sandboxed_api/sandbox2/buffer.cc
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/buffer.h"
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/strerror.h"
|
||||||
|
#include "sandboxed_api/util/canonical_errors.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
// Creates a new Buffer that is backed by the specified file descriptor.
|
||||||
|
::sapi::StatusOr<std::unique_ptr<Buffer>> Buffer::CreateFromFd(int fd) {
|
||||||
|
auto buffer = absl::WrapUnique(new Buffer{});
|
||||||
|
|
||||||
|
struct stat stat_buf;
|
||||||
|
if (fstat(fd, &stat_buf) != 0) {
|
||||||
|
return ::sapi::InternalError(
|
||||||
|
absl::StrCat("Could not stat buffer fd: ", StrError(errno)));
|
||||||
|
}
|
||||||
|
size_t size = stat_buf.st_size;
|
||||||
|
int prot = PROT_READ | PROT_WRITE;
|
||||||
|
int flags = MAP_SHARED;
|
||||||
|
off_t offset = 0;
|
||||||
|
buffer->buf_ =
|
||||||
|
reinterpret_cast<uint8_t*>(mmap(nullptr, size, prot, flags, fd, offset));
|
||||||
|
if (buffer->buf_ == MAP_FAILED) {
|
||||||
|
return ::sapi::InternalError(
|
||||||
|
absl::StrCat("Could not map buffer fd: ", StrError(errno)));
|
||||||
|
}
|
||||||
|
buffer->fd_ = fd;
|
||||||
|
buffer->size_ = size;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new Buffer of the specified size, backed by a temporary file that
|
||||||
|
// will be immediately deleted.
|
||||||
|
::sapi::StatusOr<std::unique_ptr<Buffer>> Buffer::CreateWithSize(int64_t size) {
|
||||||
|
int fd;
|
||||||
|
if (!util::CreateMemFd(&fd)) {
|
||||||
|
return ::sapi::InternalError("Could not create buffer temp file");
|
||||||
|
}
|
||||||
|
if (ftruncate(fd, size) != 0) {
|
||||||
|
return ::sapi::InternalError(
|
||||||
|
absl::StrCat("Could not extend buffer fd: ", StrError(errno)));
|
||||||
|
}
|
||||||
|
return CreateFromFd(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::~Buffer() {
|
||||||
|
if (buf_ != nullptr) {
|
||||||
|
munmap(buf_, size_);
|
||||||
|
}
|
||||||
|
if (fd_ != -1) {
|
||||||
|
close(fd_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
63
sandboxed_api/sandbox2/buffer.h
Normal file
63
sandboxed_api/sandbox2/buffer.h
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX2_BUFFER_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX2_BUFFER_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "sandboxed_api/util/statusor.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
// Buffer provides a way for executor and sandboxee to share data.
|
||||||
|
// It is useful to share large buffers instead of communicating and copying.
|
||||||
|
// The executor must distrust the content of this buffer, like everything
|
||||||
|
// else that comes under control of the sandboxee.
|
||||||
|
class Buffer final {
|
||||||
|
public:
|
||||||
|
~Buffer();
|
||||||
|
|
||||||
|
Buffer(const Buffer&) = delete;
|
||||||
|
Buffer& operator=(const Buffer&) = delete;
|
||||||
|
|
||||||
|
// Creates a new Buffer that is backed by the specified file descriptor.
|
||||||
|
static ::sapi::StatusOr<std::unique_ptr<Buffer>> CreateFromFd(int fd);
|
||||||
|
|
||||||
|
// Creates a new Buffer of the specified size, backed by a temporary file that
|
||||||
|
// will be immediately deleted.
|
||||||
|
static ::sapi::StatusOr<std::unique_ptr<Buffer>> CreateWithSize(int64_t size);
|
||||||
|
|
||||||
|
// Returns a pointer to the buffer, which is read/write.
|
||||||
|
uint8_t* data() const { return buf_; }
|
||||||
|
|
||||||
|
// Gets the size of the buffer in bytes.
|
||||||
|
size_t size() const { return size_; }
|
||||||
|
|
||||||
|
// Gets the file descriptor backing the buffer.
|
||||||
|
int fd() const { return fd_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Buffer() = default;
|
||||||
|
|
||||||
|
uint8_t* buf_ = nullptr;
|
||||||
|
int fd_ = -1;
|
||||||
|
size_t size_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX2_BUFFER_H_
|
167
sandboxed_api/sandbox2/buffer_test.cc
Normal file
167
sandboxed_api/sandbox2/buffer_test.cc
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/buffer.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/result.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
#include "sandboxed_api/sandbox2/testing.h"
|
||||||
|
#include "sandboxed_api/util/status_matchers.h"
|
||||||
|
|
||||||
|
using ::testing::Eq;
|
||||||
|
using ::testing::IsTrue;
|
||||||
|
using ::testing::Ne;
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Test all public methods of sandbox2::Buffer.
|
||||||
|
TEST(BufferTest, TestImplementation) {
|
||||||
|
constexpr int kSize = 1024;
|
||||||
|
SAPI_ASSERT_OK_AND_ASSIGN(auto buffer, Buffer::CreateWithSize(kSize));
|
||||||
|
EXPECT_THAT(buffer->size(), Eq(kSize));
|
||||||
|
uint8_t* raw_buf = buffer->data();
|
||||||
|
for (int i = 0; i < kSize; i++) {
|
||||||
|
raw_buf[i] = 'X';
|
||||||
|
}
|
||||||
|
SAPI_ASSERT_OK_AND_ASSIGN(auto buffer2, Buffer::CreateFromFd(buffer->fd()));
|
||||||
|
uint8_t* raw_buf2 = buffer2->data();
|
||||||
|
for (int i = 0; i < kSize; i++) {
|
||||||
|
EXPECT_THAT(raw_buf2[i], Eq('X'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Policy> BufferTestcasePolicy() {
|
||||||
|
auto s2p = PolicyBuilder()
|
||||||
|
.AllowStaticStartup()
|
||||||
|
.AllowExit()
|
||||||
|
.AllowSafeFcntl()
|
||||||
|
.AllowTime()
|
||||||
|
.AllowSyscall(__NR_dup)
|
||||||
|
.AllowSyscall(__NR_futex)
|
||||||
|
.AllowSyscall(__NR_getpid)
|
||||||
|
.AllowSyscall(__NR_gettid)
|
||||||
|
.AllowSystemMalloc()
|
||||||
|
.AllowRead()
|
||||||
|
.AllowWrite()
|
||||||
|
.AllowSyscall(__NR_nanosleep)
|
||||||
|
.AllowSyscall(__NR_rt_sigprocmask)
|
||||||
|
.AllowSyscall(__NR_recvmsg)
|
||||||
|
.AllowMmap()
|
||||||
|
.AllowStat()
|
||||||
|
.AllowSyscall(__NR_lseek)
|
||||||
|
.AllowSyscall(__NR_close)
|
||||||
|
.BlockSyscallWithErrno(__NR_prlimit64, EPERM)
|
||||||
|
.BlockSyscallWithErrno(__NR_open, ENOENT)
|
||||||
|
.BlockSyscallWithErrno(__NR_openat, ENOENT)
|
||||||
|
// On Debian, even static binaries check existence of
|
||||||
|
// /etc/ld.so.nohwcap.
|
||||||
|
.BlockSyscallWithErrno(__NR_access, ENOENT)
|
||||||
|
.BuildOrDie();
|
||||||
|
|
||||||
|
#if defined(__powerpc64__)
|
||||||
|
|
||||||
|
s2p->AllowUnsafeMmapFiles();
|
||||||
|
s2p->AllowUnsafeMmapShared();
|
||||||
|
#endif /* defined(__powerpc64__) */
|
||||||
|
|
||||||
|
return s2p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test sharing of buffer between executor/sandboxee using dup/MapFd.
|
||||||
|
TEST(BufferTest, TestWithSandboxeeMapFd) {
|
||||||
|
SKIP_SANITIZERS_AND_COVERAGE;
|
||||||
|
const std::string path = GetTestSourcePath("sandbox2/testcases/buffer");
|
||||||
|
std::vector<std::string> args = {path, "1"};
|
||||||
|
auto executor = absl::make_unique<Executor>(path, args);
|
||||||
|
auto policy = BufferTestcasePolicy();
|
||||||
|
|
||||||
|
SAPI_ASSERT_OK_AND_ASSIGN(auto buffer,
|
||||||
|
Buffer::CreateWithSize(1ULL << 20 /* 1MiB */));
|
||||||
|
// buffer() uses the internal fd to mmap the buffer.
|
||||||
|
uint8_t* buf = buffer->data();
|
||||||
|
// Test that we can write data to the sandboxee.
|
||||||
|
buf[0] = 'A';
|
||||||
|
|
||||||
|
// Map buffer as fd 3, but careful because MapFd closes the buffer fd and
|
||||||
|
// we need to keep it since buffer uses it for mmap, so we must dup.
|
||||||
|
executor->ipc()->MapFd(dup(buffer->fd()), 3);
|
||||||
|
|
||||||
|
Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
auto result = s2.Run();
|
||||||
|
|
||||||
|
EXPECT_THAT(result.final_status(), Eq(Result::OK));
|
||||||
|
EXPECT_THAT(result.reason_code(), Eq(0));
|
||||||
|
|
||||||
|
// Test that we can read data from the sandboxee.
|
||||||
|
EXPECT_THAT(buf[buffer->size() - 1], Eq('B'));
|
||||||
|
|
||||||
|
// Test that internal buffer fd remains valid.
|
||||||
|
struct stat stat_buf;
|
||||||
|
EXPECT_THAT(fstat(buffer->fd(), &stat_buf), Ne(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test sharing of buffer between executor/sandboxee using SendFD/RecvFD.
|
||||||
|
TEST(BufferTest, TestWithSandboxeeSendRecv) {
|
||||||
|
SKIP_SANITIZERS_AND_COVERAGE;
|
||||||
|
const std::string path = GetTestSourcePath("sandbox2/testcases/buffer");
|
||||||
|
std::vector<std::string> args = {path, "2"};
|
||||||
|
auto executor = absl::make_unique<Executor>(path, args);
|
||||||
|
auto* comms = executor->ipc()->comms();
|
||||||
|
|
||||||
|
auto policy = BufferTestcasePolicy();
|
||||||
|
|
||||||
|
Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
ASSERT_THAT(s2.RunAsync(), IsTrue());
|
||||||
|
|
||||||
|
SAPI_ASSERT_OK_AND_ASSIGN(auto buffer,
|
||||||
|
Buffer::CreateWithSize(1ULL << 20 /* 1MiB */));
|
||||||
|
uint8_t* buf = buffer->data();
|
||||||
|
// Test that we can write data to the sandboxee.
|
||||||
|
buf[0] = 'A';
|
||||||
|
EXPECT_THAT(comms->SendFD(buffer->fd()), IsTrue());
|
||||||
|
|
||||||
|
auto result = s2.AwaitResult();
|
||||||
|
EXPECT_THAT(result.final_status(), Eq(Result::OK));
|
||||||
|
EXPECT_THAT(result.reason_code(), Eq(0));
|
||||||
|
|
||||||
|
// Test that we can read data from the sandboxee.
|
||||||
|
EXPECT_THAT(buf[buffer->size() - 1], Eq('B'));
|
||||||
|
|
||||||
|
// Test that internal buffer fd remains valid.
|
||||||
|
struct stat stat_buf;
|
||||||
|
EXPECT_THAT(fstat(buffer->fd(), &stat_buf), Ne(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace sandbox2
|
259
sandboxed_api/sandbox2/client.cc
Normal file
259
sandboxed_api/sandbox2/client.cc
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Implementation file for the sandbox2::Client class.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/client.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/filter.h>
|
||||||
|
#include <linux/seccomp.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "absl/base/internal/raw_logging.h"
|
||||||
|
#include "absl/base/attributes.h"
|
||||||
|
#include "absl/base/macros.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/strings/numbers.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_join.h"
|
||||||
|
#include "absl/strings/str_split.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sanitizer.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/strerror.h"
|
||||||
|
#include "sandboxed_api/util/raw_logging.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
constexpr uint32_t Client::kClient2SandboxReady;
|
||||||
|
constexpr uint32_t Client::kSandbox2ClientDone;
|
||||||
|
constexpr const char* Client::kFDMapEnvVar;
|
||||||
|
|
||||||
|
Client::Client(Comms* comms) : comms_(comms) {
|
||||||
|
char* fdmap_envvar = getenv(kFDMapEnvVar);
|
||||||
|
if (!fdmap_envvar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::map<absl::string_view, absl::string_view> vars =
|
||||||
|
absl::StrSplit(fdmap_envvar, ',', absl::SkipEmpty());
|
||||||
|
for (const auto& var : vars) {
|
||||||
|
int fd;
|
||||||
|
SAPI_RAW_CHECK(absl::SimpleAtoi(var.second, &fd), "failed to parse fd map");
|
||||||
|
SAPI_RAW_CHECK(fd_map_.emplace(std::string{var.first}, fd).second,
|
||||||
|
"could not insert mapping into fd map (duplicate)");
|
||||||
|
}
|
||||||
|
unsetenv(kFDMapEnvVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Client::GetFdMapEnvVar() const {
|
||||||
|
return absl::StrCat(kFDMapEnvVar, "=",
|
||||||
|
absl::StrJoin(fd_map_, ",", absl::PairFormatter(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::PrepareEnvironment() {
|
||||||
|
SetUpIPC();
|
||||||
|
SetUpCwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::EnableSandbox() {
|
||||||
|
ReceivePolicy();
|
||||||
|
ApplyPolicyAndBecomeTracee();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::SandboxMeHere() {
|
||||||
|
PrepareEnvironment();
|
||||||
|
EnableSandbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::SetUpCwd() {
|
||||||
|
{
|
||||||
|
// Get the current working directory to check if we are in a mount
|
||||||
|
// namespace.
|
||||||
|
// Note: glibc 2.27 no longer returns a relative path in that case, but
|
||||||
|
// fails with ENOENT and returns a nullptr instead. The code still
|
||||||
|
// needs to run on lower version for the time being.
|
||||||
|
char cwd_buf[PATH_MAX + 1] = {0};
|
||||||
|
char* cwd = getcwd(cwd_buf, ABSL_ARRAYSIZE(cwd_buf));
|
||||||
|
SAPI_RAW_PCHECK(cwd != nullptr || errno == ENOENT,
|
||||||
|
"no current working directory");
|
||||||
|
|
||||||
|
// Outside of the mount namespace, the path is of the form
|
||||||
|
// '(unreachable)/...'. Only check for the slash, since Linux might make up
|
||||||
|
// other prefixes in the future.
|
||||||
|
if (errno == ENOENT || cwd_buf[0] != '/') {
|
||||||
|
SAPI_RAW_VLOG(1, "chdir into mount namespace, cwd was '%s'", cwd_buf);
|
||||||
|
// If we are in a mount namespace but fail to chdir, then it can lead to a
|
||||||
|
// sandbox escape -- we need to fail with FATAL if the chdir fails.
|
||||||
|
SAPI_RAW_PCHECK(chdir("/") != -1, "corrective chdir");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive the user-supplied current working directory and change into it.
|
||||||
|
std::string cwd;
|
||||||
|
SAPI_RAW_CHECK(comms_->RecvString(&cwd), "receiving working directory");
|
||||||
|
if (!cwd.empty()) {
|
||||||
|
// On the other hand this chdir can fail without a sandbox escape. It will
|
||||||
|
// probably not have the intended behavior though.
|
||||||
|
if (chdir(cwd.c_str()) == -1) {
|
||||||
|
SAPI_RAW_VLOG(
|
||||||
|
1,
|
||||||
|
"chdir(%s) failed, falling back to previous cwd or / (with "
|
||||||
|
"namespaces). Use Executor::SetCwd() to set a working directory: %s",
|
||||||
|
cwd, StrError(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::SetUpIPC() {
|
||||||
|
uint32_t num_of_fd_pairs;
|
||||||
|
SAPI_RAW_CHECK(comms_->RecvUint32(&num_of_fd_pairs),
|
||||||
|
"receiving number of fd pairs");
|
||||||
|
SAPI_RAW_CHECK(fd_map_.empty(), "fd map not empty");
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(1, "Will receive %d file descriptor pairs", num_of_fd_pairs);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < num_of_fd_pairs; ++i) {
|
||||||
|
int32_t requested_fd;
|
||||||
|
int32_t fd;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
SAPI_RAW_CHECK(comms_->RecvInt32(&requested_fd), "receiving requested fd");
|
||||||
|
SAPI_RAW_CHECK(comms_->RecvFD(&fd), "receiving current fd");
|
||||||
|
SAPI_RAW_CHECK(comms_->RecvString(&name), "receiving name string");
|
||||||
|
|
||||||
|
if (requested_fd != -1 && fd != requested_fd) {
|
||||||
|
if (requested_fd > STDERR_FILENO && fcntl(requested_fd, F_GETFD) != -1) {
|
||||||
|
// Dup2 will silently close the FD if one is already at requested_fd.
|
||||||
|
// If someone is using the deferred sandbox entry, ie. SandboxMeHere,
|
||||||
|
// the application might have something actually using that fd.
|
||||||
|
// Therefore let's log a big warning if that FD is already in use.
|
||||||
|
// Note: this check doesn't happen for STDIN,STDOUT,STDERR.
|
||||||
|
SAPI_RAW_LOG(
|
||||||
|
WARNING,
|
||||||
|
"Cloning received fd %d over %d which is already open and will "
|
||||||
|
"be silently closed. This may lead to unexpected behavior!",
|
||||||
|
fd, requested_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(1, "Cloning received fd=%d onto fd=%d", fd, requested_fd);
|
||||||
|
SAPI_RAW_PCHECK(dup2(fd, requested_fd) != -1, "");
|
||||||
|
|
||||||
|
// Close the newly received FD if it differs from the new one.
|
||||||
|
close(fd);
|
||||||
|
fd = requested_fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name.empty()) {
|
||||||
|
SAPI_RAW_CHECK(fd_map_.emplace(name, fd).second, "duplicate fd mapping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::ReceivePolicy() {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
SAPI_RAW_CHECK(comms_->RecvBytes(&bytes), "receive bytes");
|
||||||
|
policy_len_ = bytes.size();
|
||||||
|
|
||||||
|
policy_ = absl::make_unique<uint8_t[]>(policy_len_);
|
||||||
|
memcpy(policy_.get(), bytes.data(), policy_len_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::ApplyPolicyAndBecomeTracee() {
|
||||||
|
// When running under TSAN, we need to notify TSANs background thread that we
|
||||||
|
// want it to exit and wait for it to be done. When not running under TSAN,
|
||||||
|
// this function does nothing.
|
||||||
|
sanitizer::WaitForTsan();
|
||||||
|
|
||||||
|
// Creds can be received w/o synchronization, once the connection is
|
||||||
|
// established.
|
||||||
|
pid_t cred_pid;
|
||||||
|
uid_t cred_uid ABSL_ATTRIBUTE_UNUSED;
|
||||||
|
gid_t cred_gid ABSL_ATTRIBUTE_UNUSED;
|
||||||
|
SAPI_RAW_CHECK(comms_->RecvCreds(&cred_pid, &cred_uid, &cred_gid),
|
||||||
|
"receiving credentials");
|
||||||
|
|
||||||
|
SAPI_RAW_CHECK(prctl(PR_SET_DUMPABLE, 1) == 0,
|
||||||
|
"setting PR_SET_DUMPABLE flag");
|
||||||
|
if (prctl(PR_SET_PTRACER, cred_pid) == -1) {
|
||||||
|
SAPI_RAW_VLOG(1, "No YAMA on this system. Continuing");
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_RAW_CHECK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0,
|
||||||
|
"setting PR_SET_NO_NEW_PRIVS flag");
|
||||||
|
SAPI_RAW_CHECK(prctl(PR_SET_KEEPCAPS, 0) == 0,
|
||||||
|
"setting PR_SET_KEEPCAPS flag");
|
||||||
|
|
||||||
|
sock_fprog prog;
|
||||||
|
prog.len = static_cast<uint16_t>(policy_len_ / sizeof(sock_filter));
|
||||||
|
prog.filter = reinterpret_cast<sock_filter*>(policy_.get());
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(
|
||||||
|
1, "Applying policy in PID %d, sock_fprog.len: %hd entries (%d bytes)",
|
||||||
|
syscall(__NR_gettid), prog.len, policy_len_);
|
||||||
|
|
||||||
|
// Signal executor we are ready to have limits applied on us and be ptraced.
|
||||||
|
// We want limits at the last moment to avoid triggering them too early and we
|
||||||
|
// want ptrace at the last moment to avoid synchronization deadlocks.
|
||||||
|
SAPI_RAW_CHECK(comms_->SendUint32(kClient2SandboxReady),
|
||||||
|
"receiving ready signal from executor");
|
||||||
|
uint32_t ret; // wait for confirmation
|
||||||
|
SAPI_RAW_CHECK(comms_->RecvUint32(&ret),
|
||||||
|
"receving confirmation from executor");
|
||||||
|
SAPI_RAW_CHECK(ret == kSandbox2ClientDone,
|
||||||
|
"invalid confirmation from executor");
|
||||||
|
|
||||||
|
SAPI_RAW_CHECK(
|
||||||
|
syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC,
|
||||||
|
reinterpret_cast<uintptr_t>(&prog)) == 0,
|
||||||
|
"setting SECCOMP_FILTER_FLAG_TSYNC flag");
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::GetMappedFD(const std::string& name) {
|
||||||
|
auto it = fd_map_.find(name);
|
||||||
|
SAPI_RAW_CHECK(it != fd_map_.end(),
|
||||||
|
"mapped fd not found (function called twice?)");
|
||||||
|
int fd = it->second;
|
||||||
|
fd_map_.erase(it);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::HasMappedFD(const std::string& name) {
|
||||||
|
return fd_map_.find(name) != fd_map_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::SendLogsToSupervisor() {
|
||||||
|
// This LogSink will register itself and send all logs to the executor until
|
||||||
|
// the object is destroyed.
|
||||||
|
logsink_ = absl::make_unique<LogSink>(GetMappedFD(LogSink::kLogFDName));
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkProxyClient* Client::GetNetworkProxyClient() {
|
||||||
|
if (proxy_client_ == nullptr) {
|
||||||
|
proxy_client_ = absl::make_unique<NetworkProxyClient>(
|
||||||
|
GetMappedFD(NetworkProxyClient::kFDName));
|
||||||
|
}
|
||||||
|
return proxy_client_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
108
sandboxed_api/sandbox2/client.h
Normal file
108
sandboxed_api/sandbox2/client.h
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// This class should be used in the client code, in a place where sandboxing
|
||||||
|
// should be engaged.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX2_CLIENT_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX2_CLIENT_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/logsink.h"
|
||||||
|
#include "sandboxed_api/sandbox2/network_proxy_client.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
public:
|
||||||
|
// Client is ready to be sandboxed.
|
||||||
|
static constexpr uint32_t kClient2SandboxReady = 0x0A0B0C01;
|
||||||
|
// Sandbox is ready to monitor the sandboxee.
|
||||||
|
static constexpr uint32_t kSandbox2ClientDone = 0x0A0B0C02;
|
||||||
|
|
||||||
|
explicit Client(Comms* comms);
|
||||||
|
|
||||||
|
Client(const Client&) = delete;
|
||||||
|
Client& operator=(const Client&) = delete;
|
||||||
|
|
||||||
|
// Receives a sandbox policy over the comms channel and enables sandboxing.
|
||||||
|
// Using this method allows to have a sandbox-aware sandboxee perform complex
|
||||||
|
// initialization first and then enable sandboxing for actual processing.
|
||||||
|
void SandboxMeHere();
|
||||||
|
|
||||||
|
// Returns the file descriptor that was mapped to the sandboxee using
|
||||||
|
// IPC::ReceiveFd(name).
|
||||||
|
int GetMappedFD(const std::string& name);
|
||||||
|
bool HasMappedFD(const std::string& name);
|
||||||
|
|
||||||
|
// Registers a LogSink that forwards all logs to the supervisor.
|
||||||
|
void SendLogsToSupervisor();
|
||||||
|
|
||||||
|
// Returns the network proxy client and starts it if this function is called
|
||||||
|
// for the first time.
|
||||||
|
NetworkProxyClient* GetNetworkProxyClient();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Comms used for synchronization with the monitor, not owned by the object.
|
||||||
|
Comms* comms_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr const char* kFDMapEnvVar = "SB2_FD_MAPPINGS";
|
||||||
|
|
||||||
|
friend class ForkServer;
|
||||||
|
|
||||||
|
// Seccomp-bpf policy received from the monitor.
|
||||||
|
std::unique_ptr<uint8_t[]> policy_;
|
||||||
|
|
||||||
|
// Length of the policy received from the monitor.
|
||||||
|
int policy_len_;
|
||||||
|
|
||||||
|
// LogSink that forwards all log messages to the supervisor.
|
||||||
|
std::unique_ptr<LogSink> logsink_;
|
||||||
|
|
||||||
|
// NetworkProxyClient that forwards network connection requests to the
|
||||||
|
// supervisor.
|
||||||
|
std::unique_ptr<NetworkProxyClient> proxy_client_;
|
||||||
|
|
||||||
|
// In the pre-execve case, the sandboxee has to pass the information about
|
||||||
|
// file descriptors to the new process. We set an environment variable for
|
||||||
|
// this case that is parsed in the Client constructor if present.
|
||||||
|
std::map<std::string, int> fd_map_;
|
||||||
|
|
||||||
|
std::string GetFdMapEnvVar() const;
|
||||||
|
|
||||||
|
// Sets up communication channels with the sandbox.
|
||||||
|
void SetUpIPC();
|
||||||
|
|
||||||
|
// Sets up the current working directory.
|
||||||
|
void SetUpCwd();
|
||||||
|
|
||||||
|
// Receives seccomp-bpf policy from the monitor.
|
||||||
|
void ReceivePolicy();
|
||||||
|
|
||||||
|
// Applies sandbox-bpf policy, have limits applied on us, and become ptrace'd.
|
||||||
|
void ApplyPolicyAndBecomeTracee();
|
||||||
|
|
||||||
|
void PrepareEnvironment();
|
||||||
|
void EnableSandbox();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX2_CLIENT_H_
|
660
sandboxed_api/sandbox2/comms.cc
Normal file
660
sandboxed_api/sandbox2/comms.cc
Normal file
|
@ -0,0 +1,660 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Implementation of sandbox2::Comms class.
|
||||||
|
//
|
||||||
|
// Warning: This class is not multi-thread safe (for callers). It uses a single
|
||||||
|
// communications channel (an AF_UNIX socket), so it requires exactly one sender
|
||||||
|
// and one receiver. If you plan to use it from many threads, provide external
|
||||||
|
// exclusive locking.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
#include <syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "google/protobuf/message.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/synchronization/mutex.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/strerror.h"
|
||||||
|
#include "sandboxed_api/util/raw_logging.h"
|
||||||
|
#include "sandboxed_api/util/status.h"
|
||||||
|
#include "sandboxed_api/util/status_macros.h"
|
||||||
|
#include "sandboxed_api/util/statusor.h"
|
||||||
|
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
#include "base/dynamic_annotations.h"
|
||||||
|
#endif
|
||||||
|
// Future extension point used to mark code sections that invoke syscalls that
|
||||||
|
// potentially block.
|
||||||
|
// Internally at Google, there is an implementation that supports light-weight
|
||||||
|
// fibers.
|
||||||
|
class PotentiallyBlockingRegion {
|
||||||
|
public:
|
||||||
|
~PotentiallyBlockingRegion() {
|
||||||
|
// Do nothing. Not defaulted to avoid "unused variable" warnings.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool IsFatalError(int saved_errno) {
|
||||||
|
return saved_errno != EAGAIN && saved_errno != EWOULDBLOCK &&
|
||||||
|
saved_errno != EFAULT && saved_errno != EINTR &&
|
||||||
|
saved_errno != EINVAL && saved_errno != ENOMEM;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
constexpr uint32_t Comms::kTagBool;
|
||||||
|
constexpr uint32_t Comms::kTagInt8;
|
||||||
|
constexpr uint32_t Comms::kTagUint8;
|
||||||
|
constexpr uint32_t Comms::kTagInt16;
|
||||||
|
constexpr uint32_t Comms::kTagUint16;
|
||||||
|
constexpr uint32_t Comms::kTagInt32;
|
||||||
|
constexpr uint32_t Comms::kTagUint32;
|
||||||
|
constexpr uint32_t Comms::kTagInt64;
|
||||||
|
constexpr uint32_t Comms::kTagUint64;
|
||||||
|
constexpr uint32_t Comms::kTagString;
|
||||||
|
constexpr uint32_t Comms::kTagBytes;
|
||||||
|
constexpr uint32_t Comms::kTagProto2;
|
||||||
|
constexpr uint32_t Comms::kTagFd;
|
||||||
|
|
||||||
|
constexpr int Comms::kSandbox2ClientCommsFD;
|
||||||
|
|
||||||
|
Comms::Comms(const std::string& socket_name) : socket_name_(socket_name) {}
|
||||||
|
|
||||||
|
Comms::Comms(int fd) : connection_fd_(fd) {
|
||||||
|
// Generate a unique and meaningful socket name for this FD.
|
||||||
|
// Note: getpid()/gettid() are non-blocking syscalls.
|
||||||
|
socket_name_ = absl::StrFormat("sandbox2::Comms:FD=%d/PID=%d/TID=%ld", fd,
|
||||||
|
getpid(), syscall(__NR_gettid));
|
||||||
|
|
||||||
|
// File descriptor is already connected.
|
||||||
|
state_ = State::kConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
Comms::~Comms() { Terminate(); }
|
||||||
|
|
||||||
|
int Comms::GetConnectionFD() const {
|
||||||
|
return connection_fd_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::Listen() {
|
||||||
|
if (IsConnected()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_fd_ = socket(AF_UNIX, SOCK_STREAM, 0); // Non-blocking
|
||||||
|
if (bind_fd_ == -1) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "socket(AF_UNIX)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_un sus;
|
||||||
|
socklen_t slen = CreateSockaddrUn(&sus);
|
||||||
|
// bind() is non-blocking.
|
||||||
|
if (bind(bind_fd_, reinterpret_cast<sockaddr*>(&sus), slen) == -1) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "bind(bind_fd)");
|
||||||
|
|
||||||
|
// Note: checking for EINTR on close() syscall is useless and possibly
|
||||||
|
// harmful, see https://lwn.net/Articles/576478/.
|
||||||
|
{
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
close(bind_fd_);
|
||||||
|
}
|
||||||
|
bind_fd_ = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen() non-blocking.
|
||||||
|
if (listen(bind_fd_, 0) == -1) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "listen(bind_fd)");
|
||||||
|
{
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
close(bind_fd_);
|
||||||
|
}
|
||||||
|
bind_fd_ = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(1, "Listening at: %s", socket_name_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::Accept() {
|
||||||
|
if (IsConnected()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_un suc;
|
||||||
|
socklen_t len = sizeof(suc);
|
||||||
|
{
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
connection_fd_ = TEMP_FAILURE_RETRY(
|
||||||
|
accept(bind_fd_, reinterpret_cast<sockaddr*>(&suc), &len));
|
||||||
|
}
|
||||||
|
if (connection_fd_ == -1) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "accept(bind_fd)");
|
||||||
|
{
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
close(bind_fd_);
|
||||||
|
}
|
||||||
|
bind_fd_ = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state_ = State::kConnected;
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(1, "Accepted connection at: %s, fd: %d", socket_name_,
|
||||||
|
connection_fd_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::Connect() {
|
||||||
|
if (IsConnected()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection_fd_ = socket(AF_UNIX, SOCK_STREAM, 0); // Non-blocking
|
||||||
|
if (connection_fd_ == -1) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "socket(AF_UNIX)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_un suc;
|
||||||
|
socklen_t slen = CreateSockaddrUn(&suc);
|
||||||
|
int ret;
|
||||||
|
{
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
ret = TEMP_FAILURE_RETRY(
|
||||||
|
connect(connection_fd_, reinterpret_cast<sockaddr*>(&suc), slen));
|
||||||
|
}
|
||||||
|
if (ret == -1) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "connect(connection_fd)");
|
||||||
|
{
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
close(connection_fd_);
|
||||||
|
}
|
||||||
|
connection_fd_ = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state_ = State::kConnected;
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(1, "Connected to: %s, fd: %d", socket_name_, connection_fd_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All member variables touched by Terminate() should be marked as 'mutable'.
|
||||||
|
void Comms::Terminate() {
|
||||||
|
{
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
|
||||||
|
state_ = State::kTerminated;
|
||||||
|
|
||||||
|
if (bind_fd_ != -1) {
|
||||||
|
close(bind_fd_);
|
||||||
|
bind_fd_ = -1;
|
||||||
|
}
|
||||||
|
if (connection_fd_ != -1) {
|
||||||
|
close(connection_fd_);
|
||||||
|
connection_fd_ = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::SendTLV(uint32_t tag, uint64_t length, const uint8_t* bytes) {
|
||||||
|
if (length > GetMaxMsgSize()) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Maximum TLV message size exceeded: (%u > %u)", length,
|
||||||
|
GetMaxMsgSize());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (length > kWarnMsgSize) {
|
||||||
|
static int times_warned = 0;
|
||||||
|
if (times_warned < 10) {
|
||||||
|
++times_warned;
|
||||||
|
SAPI_RAW_LOG(WARNING,
|
||||||
|
"TLV message of size %u detected. Please consider switching "
|
||||||
|
"to Buffer API instead.",
|
||||||
|
length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(3, "Sending a TLV message, tag: 0x%08x, length: %u", tag,
|
||||||
|
length);
|
||||||
|
{
|
||||||
|
absl::MutexLock lock(&tlv_send_transmission_mutex_);
|
||||||
|
if (!Send(reinterpret_cast<uint8_t*>(&tag), sizeof(tag))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Send(reinterpret_cast<uint8_t*>(&length), sizeof(length))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (length > 0) {
|
||||||
|
if (!Send(bytes, length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvString(std::string* v) {
|
||||||
|
TLV tlv;
|
||||||
|
if (!RecvTLV(&tlv)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tlv.tag != kTagString) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Expected (kTagString == 0x%x), got: 0x%x", kTagString,
|
||||||
|
tlv.tag);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
v->assign(reinterpret_cast<const char*>(tlv.value.data()), tlv.value.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::SendString(const std::string& v) {
|
||||||
|
return SendTLV(kTagString, v.length(),
|
||||||
|
reinterpret_cast<const uint8_t*>(v.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvBytes(std::vector<uint8_t>* buffer) {
|
||||||
|
TLV tlv;
|
||||||
|
if (!RecvTLV(&tlv)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tlv.tag != kTagBytes) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Expected (kTagBytes == 0x%x), got: 0x%u", kTagBytes,
|
||||||
|
tlv.tag);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buffer->swap(tlv.value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::SendBytes(const uint8_t* v, uint64_t len) {
|
||||||
|
return SendTLV(kTagBytes, len, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::SendBytes(const std::vector<uint8_t>& buffer) {
|
||||||
|
return SendBytes(buffer.data(), buffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvCreds(pid_t* pid, uid_t* uid, gid_t* gid) {
|
||||||
|
ucred uc;
|
||||||
|
socklen_t sls = sizeof(uc);
|
||||||
|
int rc;
|
||||||
|
{
|
||||||
|
// Not completely sure if getsockopt() can block on SO_PEERCRED, but let's
|
||||||
|
// play it safe.
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
rc = getsockopt(GetConnectionFD(), SOL_SOCKET, SO_PEERCRED, &uc, &sls);
|
||||||
|
}
|
||||||
|
if (rc == -1) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "getsockopt(SO_PEERCRED)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*pid = uc.pid;
|
||||||
|
*uid = uc.uid;
|
||||||
|
*gid = uc.gid;
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(2, "Received credentials from PID/UID/GID: %d/%u/%u", *pid,
|
||||||
|
*uid, *gid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvFD(int* fd) {
|
||||||
|
char fd_msg[8192];
|
||||||
|
cmsghdr* cmsg = reinterpret_cast<cmsghdr*>(fd_msg);
|
||||||
|
|
||||||
|
InternalTLV tlv;
|
||||||
|
iovec iov = {&tlv, sizeof(tlv)};
|
||||||
|
|
||||||
|
msghdr msg;
|
||||||
|
msg.msg_name = nullptr;
|
||||||
|
msg.msg_namelen = 0;
|
||||||
|
msg.msg_iov = &iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
msg.msg_control = cmsg;
|
||||||
|
msg.msg_controllen = sizeof(fd_msg);
|
||||||
|
msg.msg_flags = 0;
|
||||||
|
|
||||||
|
const auto op = [&msg](int fd) -> ssize_t {
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
// Use syscall, otherwise we would need to whitelist socketcall() on PPC.
|
||||||
|
return TEMP_FAILURE_RETRY(
|
||||||
|
util::Syscall(__NR_recvmsg, fd, reinterpret_cast<uintptr_t>(&msg), 0));
|
||||||
|
};
|
||||||
|
ssize_t len;
|
||||||
|
len = op(connection_fd_);
|
||||||
|
if (len < 0) {
|
||||||
|
if (IsFatalError(errno)) {
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
SAPI_RAW_PLOG(ERROR, "recvmsg(SCM_RIGHTS)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len == 0) {
|
||||||
|
Terminate();
|
||||||
|
SAPI_RAW_VLOG(1, "RecvFD: end-point terminated the connection.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len != sizeof(tlv)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Expected size: %u, got %d", sizeof(tlv), len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// At this point, we know that op() has been called successfully, therefore
|
||||||
|
// msg struct has been fully populated. Apparently MSAN is not aware of
|
||||||
|
// syscall(__NR_recvmsg) semantics so we need to suppress the error (here and
|
||||||
|
// everywhere below).
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
ANNOTATE_MEMORY_IS_INITIALIZED(&tlv, sizeof(tlv));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (tlv.tag != kTagFd) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Expected (kTagFD: 0x%x), got: 0x%u", kTagFd, tlv.tag);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmsg = CMSG_FIRSTHDR(&msg);
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
ANNOTATE_MEMORY_IS_INITIALIZED(cmsg, sizeof(cmsghdr));
|
||||||
|
#endif
|
||||||
|
while (cmsg) {
|
||||||
|
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
|
||||||
|
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
|
||||||
|
SAPI_RAW_VLOG(1,
|
||||||
|
"recvmsg(SCM_RIGHTS): cmsg->cmsg_len != "
|
||||||
|
"CMSG_LEN(sizeof(int)), skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int* fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
|
||||||
|
*fd = fds[0];
|
||||||
|
#ifdef MEMORY_SANITIZER
|
||||||
|
ANNOTATE_MEMORY_IS_INITIALIZED(fd, sizeof(int));
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cmsg = CMSG_NXTHDR(&msg, cmsg);
|
||||||
|
}
|
||||||
|
SAPI_RAW_LOG(ERROR,
|
||||||
|
"Haven't received the SCM_RIGHTS message, process is probably "
|
||||||
|
"out of free file descriptors");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::SendFD(int fd) {
|
||||||
|
char fd_msg[CMSG_SPACE(sizeof(int))] = {0};
|
||||||
|
cmsghdr* cmsg = reinterpret_cast<cmsghdr*>(fd_msg);
|
||||||
|
cmsg->cmsg_level = SOL_SOCKET;
|
||||||
|
cmsg->cmsg_type = SCM_RIGHTS;
|
||||||
|
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
|
||||||
|
|
||||||
|
int* fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
|
||||||
|
fds[0] = fd;
|
||||||
|
|
||||||
|
InternalTLV tlv = {kTagFd, sizeof(tlv.val), 0};
|
||||||
|
|
||||||
|
iovec iov;
|
||||||
|
iov.iov_base = &tlv;
|
||||||
|
iov.iov_len = sizeof(tlv);
|
||||||
|
|
||||||
|
msghdr msg;
|
||||||
|
msg.msg_name = nullptr;
|
||||||
|
msg.msg_namelen = 0;
|
||||||
|
msg.msg_iov = &iov;
|
||||||
|
msg.msg_iovlen = 1;
|
||||||
|
msg.msg_control = cmsg;
|
||||||
|
msg.msg_controllen = sizeof(fd_msg);
|
||||||
|
msg.msg_flags = 0;
|
||||||
|
|
||||||
|
const auto op = [&msg](int fd) -> ssize_t {
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
// Use syscall, otherwise we would need to whitelist socketcall() on PPC.
|
||||||
|
return TEMP_FAILURE_RETRY(
|
||||||
|
util::Syscall(__NR_sendmsg, fd, reinterpret_cast<uintptr_t>(&msg), 0));
|
||||||
|
};
|
||||||
|
ssize_t len;
|
||||||
|
len = op(connection_fd_);
|
||||||
|
if (len == -1 && errno == EPIPE) {
|
||||||
|
Terminate();
|
||||||
|
SAPI_RAW_LOG(ERROR, "sendmsg(SCM_RIGHTS): Peer disconnected");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len < 0) {
|
||||||
|
if (IsFatalError(errno)) {
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
SAPI_RAW_PLOG(ERROR, "sendmsg(SCM_RIGHTS)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len != sizeof(tlv)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Expected to send %u bytes, sent %d", sizeof(tlv), len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvProtoBuf(google::protobuf::Message* message) {
|
||||||
|
TLV tlv;
|
||||||
|
if (!RecvTLV(&tlv)) {
|
||||||
|
if (IsConnected()) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "RecvProtoBuf failed for (%s)", socket_name_);
|
||||||
|
} else {
|
||||||
|
Terminate();
|
||||||
|
SAPI_RAW_VLOG(2, "Connection terminated (%s)", socket_name_);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tlv.tag != kTagProto2) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Expected tag: 0x%x, got: 0x%u", kTagProto2, tlv.tag);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return message->ParseFromArray(tlv.value.data(), tlv.value.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::SendProtoBuf(const google::protobuf::Message& message) {
|
||||||
|
std::string str;
|
||||||
|
if (!message.SerializeToString(&str)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Couldn't serialize the ProtoBuf");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SendTLV(kTagProto2, str.length(),
|
||||||
|
reinterpret_cast<const uint8_t*>(str.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// All methods below are private, for internal use only.
|
||||||
|
// *****************************************************************************
|
||||||
|
|
||||||
|
socklen_t Comms::CreateSockaddrUn(sockaddr_un* sun) {
|
||||||
|
sun->sun_family = AF_UNIX;
|
||||||
|
bzero(sun->sun_path, sizeof(sun->sun_path));
|
||||||
|
// Create an 'abstract socket address' by specifying a leading null byte. The
|
||||||
|
// remainder of the path is used as a unique name, but no file is created on
|
||||||
|
// the filesystem. No need to NUL-terminate the std::string.
|
||||||
|
// See `man 7 unix` for further explanation.
|
||||||
|
strncpy(&sun->sun_path[1], socket_name_.c_str(), sizeof(sun->sun_path) - 1);
|
||||||
|
|
||||||
|
// Len is complicated - it's essentially size of the path, plus initial
|
||||||
|
// NUL-byte, minus size of the sun.sun_family.
|
||||||
|
socklen_t slen = sizeof(sun->sun_family) + strlen(socket_name_.c_str()) + 1;
|
||||||
|
if (slen > sizeof(sockaddr_un)) {
|
||||||
|
slen = sizeof(sockaddr_un);
|
||||||
|
}
|
||||||
|
return slen;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::Send(const uint8_t* bytes, uint64_t len) {
|
||||||
|
uint64_t total_sent = 0;
|
||||||
|
const auto op = [bytes, len, &total_sent](int fd) -> ssize_t {
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
return TEMP_FAILURE_RETRY(write(fd, &bytes[total_sent], len - total_sent));
|
||||||
|
};
|
||||||
|
while (total_sent < len) {
|
||||||
|
ssize_t s;
|
||||||
|
s = op(connection_fd_);
|
||||||
|
if (s == -1 && errno == EPIPE) {
|
||||||
|
Terminate();
|
||||||
|
// We do not expect the other end to disappear.
|
||||||
|
SAPI_RAW_LOG(ERROR, "Send: end-point terminated the connection");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (s == -1) {
|
||||||
|
if (IsFatalError(errno)) {
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
SAPI_RAW_PLOG(ERROR, "write");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (s == 0) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Couldn't write more bytes, wrote: %u, requested: %u",
|
||||||
|
total_sent, len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
total_sent += s;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::Recv(uint8_t* bytes, uint64_t len) {
|
||||||
|
uint64_t total_recv = 0;
|
||||||
|
const auto op = [bytes, len, &total_recv](int fd) -> ssize_t {
|
||||||
|
PotentiallyBlockingRegion region;
|
||||||
|
return TEMP_FAILURE_RETRY(read(fd, &bytes[total_recv], len - total_recv));
|
||||||
|
};
|
||||||
|
while (total_recv < len) {
|
||||||
|
ssize_t s;
|
||||||
|
s = op(connection_fd_);
|
||||||
|
if (s == -1) {
|
||||||
|
if (IsFatalError(errno)) {
|
||||||
|
Terminate();
|
||||||
|
}
|
||||||
|
SAPI_RAW_PLOG(ERROR, "read");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (s == 0) {
|
||||||
|
Terminate();
|
||||||
|
// The other end might have finished its work.
|
||||||
|
SAPI_RAW_VLOG(2, "Recv: end-point terminated the connection.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
total_recv += s;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal helper method (low level).
|
||||||
|
bool Comms::RecvTL(uint32_t* tag, uint64_t* length) {
|
||||||
|
if (!Recv(reinterpret_cast<uint8_t*>(tag), sizeof(*tag))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Recv(reinterpret_cast<uint8_t*>(length), sizeof(*length))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*length > GetMaxMsgSize()) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Maximum TLV message size exceeded: (%u > %d)", *length,
|
||||||
|
GetMaxMsgSize());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*length > kWarnMsgSize) {
|
||||||
|
static int times_warned = 0;
|
||||||
|
if (times_warned < 10) {
|
||||||
|
++times_warned;
|
||||||
|
SAPI_RAW_LOG(
|
||||||
|
WARNING,
|
||||||
|
"TLV message of size: (%u detected. Please consider switching to "
|
||||||
|
"Buffer API instead.",
|
||||||
|
*length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvTLV(TLV* tlv) {
|
||||||
|
absl::MutexLock lock(&tlv_recv_transmission_mutex_);
|
||||||
|
uint64_t length;
|
||||||
|
if (!RecvTL(&tlv->tag, &length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tlv->value.resize(length);
|
||||||
|
return length == 0 || Recv(tlv->value.data(), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvTLV(uint32_t* tag, std::vector<uint8_t>* value) {
|
||||||
|
absl::MutexLock lock(&tlv_recv_transmission_mutex_);
|
||||||
|
uint64_t length;
|
||||||
|
if (!RecvTL(tag, &length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value->resize(length);
|
||||||
|
return length == 0 || Recv(value->data(), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvTLV(uint32_t* tag, uint64_t* length, void* buffer,
|
||||||
|
uint64_t buffer_size) {
|
||||||
|
absl::MutexLock lock(&tlv_recv_transmission_mutex_);
|
||||||
|
if (!RecvTL(tag, length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*length > buffer_size) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Buffer size too small (0x%x > 0x%x)", *length,
|
||||||
|
buffer_size);
|
||||||
|
return false;
|
||||||
|
} else if (*length > 0) {
|
||||||
|
if (!Recv(reinterpret_cast<uint8_t*>(buffer), *length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Comms::RecvInt(void* buffer, uint64_t len, uint32_t tag) {
|
||||||
|
uint32_t received_tag;
|
||||||
|
uint64_t received_length;
|
||||||
|
if (!RecvTLV(&received_tag, &received_length, buffer, len)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (received_tag != tag) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Expected tag: 0x%08x, got: 0x%x", tag, received_tag);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (received_length != len) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Expected length: %u, got: %u", len, received_length);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
241
sandboxed_api/sandbox2/comms.h
Normal file
241
sandboxed_api/sandbox2/comms.h
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// The sandbox2::Comms class uses AF_UNIX sockets in the abstract namespace
|
||||||
|
// (man 7 unix) to send pieces of data between processes. It uses the TLV
|
||||||
|
// encoding and provides some useful helpers.
|
||||||
|
//
|
||||||
|
// The endianess is platform-specific, but as it can be used over abstract
|
||||||
|
// sockets only, that's not a problem. Is some poor soul decides to rewrite it
|
||||||
|
// to work over AF_INET(6), the endianess will have to be dealt with (somehow).
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX2_COMMS_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX2_COMMS_H_
|
||||||
|
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "absl/base/attributes.h"
|
||||||
|
#include "absl/synchronization/mutex.h"
|
||||||
|
#include "sandboxed_api/util/status.h"
|
||||||
|
|
||||||
|
namespace proto2 {
|
||||||
|
class Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
class Comms {
|
||||||
|
public:
|
||||||
|
// Default tags, custom tags should be <0x80000000.
|
||||||
|
static constexpr uint32_t kTagBool = 0x80000001;
|
||||||
|
static constexpr uint32_t kTagInt8 = 0x80000002;
|
||||||
|
static constexpr uint32_t kTagUint8 = 0x80000003;
|
||||||
|
static constexpr uint32_t kTagInt16 = 0x80000004;
|
||||||
|
static constexpr uint32_t kTagUint16 = 0x80000005;
|
||||||
|
static constexpr uint32_t kTagInt32 = 0x80000006;
|
||||||
|
static constexpr uint32_t kTagUint32 = 0x80000007;
|
||||||
|
static constexpr uint32_t kTagInt64 = 0x80000008;
|
||||||
|
static constexpr uint32_t kTagUint64 = 0x80000009;
|
||||||
|
static constexpr uint32_t kTagString = 0x80000100;
|
||||||
|
static constexpr uint32_t kTagBytes = 0x80000101;
|
||||||
|
static constexpr uint32_t kTagProto2 = 0x80000102;
|
||||||
|
static constexpr uint32_t kTagFd = 0X80000201;
|
||||||
|
|
||||||
|
// Any payload size above this limit will LOG(WARNING).
|
||||||
|
static constexpr uint64_t kWarnMsgSize = (256ULL << 20);
|
||||||
|
|
||||||
|
// Sandbox2-specific convention where FD=1023 is always passed to the
|
||||||
|
// sandboxed process as a communication channel (encapsulated in the
|
||||||
|
// sandbox2::Comms object at the server-side).
|
||||||
|
static constexpr int kSandbox2ClientCommsFD = 1023;
|
||||||
|
|
||||||
|
// This object will have to be connected later on.
|
||||||
|
explicit Comms(const std::string& socket_name);
|
||||||
|
|
||||||
|
Comms(const Comms&) = delete;
|
||||||
|
Comms& operator=(const Comms&) = delete;
|
||||||
|
|
||||||
|
// Instantiates a pre-connected object.
|
||||||
|
// Takes ownership over fd, which will be closed on object's destruction.
|
||||||
|
explicit Comms(int fd);
|
||||||
|
|
||||||
|
~Comms();
|
||||||
|
|
||||||
|
// Binds to an address and make it listen to connections.
|
||||||
|
bool Listen();
|
||||||
|
|
||||||
|
// Accepts the connection.
|
||||||
|
bool Accept();
|
||||||
|
|
||||||
|
// Connects to a remote socket.
|
||||||
|
bool Connect();
|
||||||
|
|
||||||
|
// Terminates all underlying file descriptors, and sets the status of the
|
||||||
|
// Comms object to TERMINATED.
|
||||||
|
void Terminate();
|
||||||
|
|
||||||
|
// Returns the already connected FD.
|
||||||
|
int GetConnectionFD() const;
|
||||||
|
|
||||||
|
bool IsConnected() const { return state_ == State::kConnected; }
|
||||||
|
bool IsTerminated() const { return state_ == State::kTerminated; }
|
||||||
|
|
||||||
|
// Returns the maximum size of a message that can be send over the comms
|
||||||
|
// channel.
|
||||||
|
// Note: The actual size is "unlimited", although the Buffer API is more
|
||||||
|
// efficient for large transfers. There is an arbitrary limit to ~2GiB to
|
||||||
|
// avoid protobuf serialization issues.
|
||||||
|
uint64_t GetMaxMsgSize() const { return std::numeric_limits<int32_t>::max(); }
|
||||||
|
|
||||||
|
bool SendTLV(uint32_t tag, uint64_t length, const uint8_t* bytes);
|
||||||
|
// Receive a TLV structure, the memory for the value will be allocated
|
||||||
|
// by std::vector.
|
||||||
|
bool RecvTLV(uint32_t* tag, std::vector<uint8_t>* value);
|
||||||
|
// Receives a TLV value into a specified buffer without allocating memory.
|
||||||
|
bool RecvTLV(uint32_t* tag, uint64_t* length, void* buffer, uint64_t buffer_size);
|
||||||
|
|
||||||
|
// Sends/receives various types of data.
|
||||||
|
bool RecvUint8(uint8_t* v) { return RecvIntGeneric(v, kTagUint8); }
|
||||||
|
bool SendUint8(uint8_t v) { return SendGeneric(v, kTagUint8); }
|
||||||
|
bool RecvInt8(int8_t* v) { return RecvIntGeneric(v, kTagInt8); }
|
||||||
|
bool SendInt8(int8_t v) { return SendGeneric(v, kTagInt8); }
|
||||||
|
bool RecvUint16(uint16_t* v) { return RecvIntGeneric(v, kTagUint16); }
|
||||||
|
bool SendUint16(uint16_t v) { return SendGeneric(v, kTagUint16); }
|
||||||
|
bool RecvInt16(int16_t* v) { return RecvIntGeneric(v, kTagInt16); }
|
||||||
|
bool SendInt16(int16_t v) { return SendGeneric(v, kTagInt16); }
|
||||||
|
bool RecvUint32(uint32_t* v) { return RecvIntGeneric(v, kTagUint32); }
|
||||||
|
bool SendUint32(uint32_t v) { return SendGeneric(v, kTagUint32); }
|
||||||
|
bool RecvInt32(int32_t* v) { return RecvIntGeneric(v, kTagInt32); }
|
||||||
|
bool SendInt32(int32_t v) { return SendGeneric(v, kTagInt32); }
|
||||||
|
bool RecvUint64(uint64_t* v) { return RecvIntGeneric(v, kTagUint64); }
|
||||||
|
bool SendUint64(uint64_t v) { return SendGeneric(v, kTagUint64); }
|
||||||
|
bool RecvInt64(int64_t* v) { return RecvIntGeneric(v, kTagInt64); }
|
||||||
|
bool SendInt64(int64_t v) { return SendGeneric(v, kTagInt64); }
|
||||||
|
bool RecvBool(bool* v) { return RecvIntGeneric(v, kTagBool); }
|
||||||
|
bool SendBool(bool v) { return SendGeneric(v, kTagBool); }
|
||||||
|
bool RecvString(std::string* v);
|
||||||
|
bool SendString(const std::string& v);
|
||||||
|
|
||||||
|
bool RecvBytes(std::vector<uint8_t>* buffer);
|
||||||
|
bool SendBytes(const uint8_t* v, uint64_t len);
|
||||||
|
bool SendBytes(const std::vector<uint8_t>& buffer);
|
||||||
|
|
||||||
|
// Receives remote process credentials.
|
||||||
|
bool RecvCreds(pid_t* pid, uid_t* uid, gid_t* gid);
|
||||||
|
|
||||||
|
// Receives/sends file descriptors.
|
||||||
|
bool RecvFD(int* fd);
|
||||||
|
bool SendFD(int fd);
|
||||||
|
|
||||||
|
// Receives/sends protobufs.
|
||||||
|
bool RecvProtoBuf(google::protobuf::Message* message);
|
||||||
|
bool SendProtoBuf(const google::protobuf::Message& message);
|
||||||
|
|
||||||
|
// Receives/sends Status objects.
|
||||||
|
template <typename StatusT>
|
||||||
|
bool RecvStatus(StatusT* status);
|
||||||
|
template <typename StatusT>
|
||||||
|
bool SendStatus(const StatusT& status);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// State of the channel
|
||||||
|
enum class State {
|
||||||
|
kUnconnected = 0,
|
||||||
|
kConnected,
|
||||||
|
kTerminated,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Connection parameters.
|
||||||
|
std::string socket_name_;
|
||||||
|
int connection_fd_ = -1;
|
||||||
|
int bind_fd_ = -1;
|
||||||
|
|
||||||
|
// Mutex making sure that we serialize TLV messages (which consist out of
|
||||||
|
// three different calls to send / receive).
|
||||||
|
absl::Mutex tlv_send_transmission_mutex_;
|
||||||
|
absl::Mutex tlv_recv_transmission_mutex_;
|
||||||
|
|
||||||
|
// State of the channel (enum), socket will have to be connected later on.
|
||||||
|
State state_ = State::kUnconnected;
|
||||||
|
|
||||||
|
// TLV structure used to pass messages around.
|
||||||
|
struct TLV {
|
||||||
|
uint32_t tag;
|
||||||
|
std::vector<uint8_t> value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Special struct for passing credentials or FDs. Different from the one above
|
||||||
|
// as it inlines the value. This is important as the data is transmitted using
|
||||||
|
// sendmsg/recvmsg instead of send/recv.
|
||||||
|
struct ABSL_ATTRIBUTE_PACKED InternalTLV {
|
||||||
|
uint32_t tag;
|
||||||
|
uint32_t len;
|
||||||
|
uint64_t val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fills sockaddr_un struct with proper values.
|
||||||
|
socklen_t CreateSockaddrUn(sockaddr_un* sun);
|
||||||
|
|
||||||
|
// Support for EINTR and size completion.
|
||||||
|
bool Send(const uint8_t* bytes, uint64_t len);
|
||||||
|
bool Recv(uint8_t* bytes, uint64_t len);
|
||||||
|
|
||||||
|
// Receives tag and length. Assumes that the `tlv_transmission_mutex_` mutex
|
||||||
|
// is locked.
|
||||||
|
bool RecvTL(uint32_t* tag, uint64_t* length)
|
||||||
|
EXCLUSIVE_LOCKS_REQUIRED(tlv_recv_transmission_mutex_);
|
||||||
|
|
||||||
|
// Receives whole TLV structure, allocates memory for the data.
|
||||||
|
bool RecvTLV(TLV* tlv);
|
||||||
|
|
||||||
|
// Receives arbitrary integers.
|
||||||
|
bool RecvInt(void* buffer, uint64_t len, uint32_t tag);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool RecvIntGeneric(T* output, uint32_t tag) {
|
||||||
|
return RecvInt(output, sizeof(T), tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool SendGeneric(T value, uint32_t tag) {
|
||||||
|
return SendTLV(tag, sizeof(T), reinterpret_cast<const uint8_t*>(&value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename StatusT>
|
||||||
|
bool Comms::RecvStatus(StatusT* status) {
|
||||||
|
sapi::StatusProto proto;
|
||||||
|
if (!RecvProtoBuf(&proto)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*status = sapi::MakeStatusFromProto(proto);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename StatusT>
|
||||||
|
bool Comms::SendStatus(const StatusT& status) {
|
||||||
|
sapi::StatusProto proto;
|
||||||
|
sapi::SaveStatusToProto(status, &proto);
|
||||||
|
return SendProtoBuf(proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX2_COMMS_H_
|
503
sandboxed_api/sandbox2/comms_test.cc
Normal file
503
sandboxed_api/sandbox2/comms_test.cc
Normal file
|
@ -0,0 +1,503 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Unittest for the sandbox2::Comms class.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <thread> // NOLINT(build/c++11)
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "google/protobuf/text_format.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "absl/container/fixed_array.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms_test.pb.h"
|
||||||
|
#include "sandboxed_api/util/status_matchers.h"
|
||||||
|
|
||||||
|
using ::sapi::IsOk;
|
||||||
|
using ::sapi::StatusIs;
|
||||||
|
using ::testing::Eq;
|
||||||
|
using ::testing::IsFalse;
|
||||||
|
using ::testing::IsTrue;
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
using CommunicationHandler = std::function<void(Comms* comms)>;
|
||||||
|
|
||||||
|
class CommsTest : public ::testing::Test {
|
||||||
|
void SetUp() override {
|
||||||
|
// Comms channel using an abstract socket namespace (initialized with socket
|
||||||
|
// name).
|
||||||
|
timespec ts1, ts2;
|
||||||
|
CHECK_NE(clock_gettime(CLOCK_REALTIME, &ts1), -1);
|
||||||
|
CHECK_NE(clock_gettime(CLOCK_REALTIME, &ts2), -1);
|
||||||
|
snprintf(sockname_, sizeof(sockname_), "comms-test-%u-%u-%u-%u",
|
||||||
|
static_cast<uint32_t>(ts1.tv_sec), static_cast<uint32_t>(ts1.tv_nsec),
|
||||||
|
static_cast<uint32_t>(ts2.tv_sec), static_cast<uint32_t>(ts2.tv_nsec));
|
||||||
|
LOG(INFO) << "Sockname: " << sockname_;
|
||||||
|
|
||||||
|
// Comms channel using a descriptor (initialized with a file descriptor).
|
||||||
|
int sv[2];
|
||||||
|
CHECK_NE(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), -1);
|
||||||
|
fd_server_ = sv[0];
|
||||||
|
fd_client_ = sv[1];
|
||||||
|
LOG(INFO) << "FD(client): " << fd_client_ << ", FD(server): " << fd_server_;
|
||||||
|
}
|
||||||
|
void TearDown() override {
|
||||||
|
close(fd_server_);
|
||||||
|
close(fd_client_);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
char sockname_[256];
|
||||||
|
int fd_client_;
|
||||||
|
int fd_server_;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr char kProtoStr[] = "value: \"ABCD\"\n";
|
||||||
|
static const absl::string_view NullTestString() {
|
||||||
|
static constexpr char kHelperStr[] = "test\0\n\r\t\x01\x02";
|
||||||
|
return absl::string_view(kHelperStr, sizeof(kHelperStr) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function that handles the communication between the two handler
|
||||||
|
// functions.
|
||||||
|
void HandleCommunication(const std::string& socketname,
|
||||||
|
const CommunicationHandler& a,
|
||||||
|
const CommunicationHandler& b) {
|
||||||
|
Comms comms(socketname);
|
||||||
|
comms.Listen();
|
||||||
|
|
||||||
|
// Start handler a.
|
||||||
|
std::thread remote([&socketname, &a]() {
|
||||||
|
Comms my_comms(socketname);
|
||||||
|
CHECK(my_comms.Connect());
|
||||||
|
a(&my_comms);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Accept connection and run handler b.
|
||||||
|
CHECK(comms.Accept());
|
||||||
|
b(&comms);
|
||||||
|
remote.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecv8) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Send Uint8.
|
||||||
|
ASSERT_THAT(comms->SendUint8(192), IsTrue());
|
||||||
|
|
||||||
|
// Recv Int8.
|
||||||
|
int8_t tmp8;
|
||||||
|
ASSERT_THAT(comms->RecvInt8(&tmp8), IsTrue());
|
||||||
|
EXPECT_THAT(tmp8, Eq(-7));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Recv Uint8.
|
||||||
|
uint8_t tmpu8;
|
||||||
|
ASSERT_THAT(comms->RecvUint8(&tmpu8), IsTrue());
|
||||||
|
EXPECT_THAT(tmpu8, Eq(192));
|
||||||
|
|
||||||
|
// Send Int8.
|
||||||
|
ASSERT_THAT(comms->SendInt8(-7), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecv16) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Send Uint16.
|
||||||
|
ASSERT_THAT(comms->SendUint16(40001), IsTrue());
|
||||||
|
|
||||||
|
// Recv Int16.
|
||||||
|
int16_t tmp16;
|
||||||
|
ASSERT_THAT(comms->RecvInt16(&tmp16), IsTrue());
|
||||||
|
EXPECT_THAT(tmp16, Eq(-22050));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Recv Uint16.
|
||||||
|
uint16_t tmpu16;
|
||||||
|
ASSERT_THAT(comms->RecvUint16(&tmpu16), IsTrue());
|
||||||
|
EXPECT_THAT(tmpu16, Eq(40001));
|
||||||
|
|
||||||
|
// Send Int16.
|
||||||
|
ASSERT_THAT(comms->SendInt16(-22050), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecv32) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// SendUint32.
|
||||||
|
ASSERT_THAT(comms->SendUint32(3221225472UL), IsTrue());
|
||||||
|
|
||||||
|
// Recv Int32.
|
||||||
|
int32_t tmp32;
|
||||||
|
ASSERT_THAT(comms->RecvInt32(&tmp32), IsTrue());
|
||||||
|
EXPECT_THAT(tmp32, Eq(-1073741824));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Recv Uint32.
|
||||||
|
uint32_t tmpu32;
|
||||||
|
ASSERT_THAT(comms->RecvUint32(&tmpu32), IsTrue());
|
||||||
|
EXPECT_THAT(tmpu32, Eq(3221225472UL));
|
||||||
|
|
||||||
|
// Send Int32.
|
||||||
|
ASSERT_THAT(comms->SendInt32(-1073741824), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecv64) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// SendUint64.
|
||||||
|
ASSERT_THAT(comms->SendUint64(1099511627776ULL), IsTrue());
|
||||||
|
|
||||||
|
// Recv Int64.
|
||||||
|
int64_t tmp64;
|
||||||
|
ASSERT_THAT(comms->RecvInt64(&tmp64), IsTrue());
|
||||||
|
EXPECT_THAT(tmp64, Eq(-1099511627776LL));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Recv Uint64.
|
||||||
|
uint64_t tmpu64;
|
||||||
|
ASSERT_THAT(comms->RecvUint64(&tmpu64), IsTrue());
|
||||||
|
EXPECT_THAT(tmpu64, Eq(1099511627776ULL));
|
||||||
|
|
||||||
|
// Send Int64.
|
||||||
|
ASSERT_THAT(comms->SendInt64(-1099511627776LL), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestTypeMismatch) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
uint8_t tmpu8;
|
||||||
|
// Receive Int8 (but Uint8 expected).
|
||||||
|
EXPECT_THAT(comms->RecvUint8(&tmpu8), IsFalse());
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send Int8 (but Uint8 expected).
|
||||||
|
ASSERT_THAT(comms->SendInt8(-93), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvString) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
std::string tmps;
|
||||||
|
ASSERT_THAT(comms->RecvString(&tmps), IsTrue());
|
||||||
|
EXPECT_TRUE(tmps == NullTestString());
|
||||||
|
EXPECT_THAT(tmps.size(), Eq(NullTestString().size()));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
ASSERT_THAT(comms->SendString(std::string(NullTestString())), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvArray) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Receive 1M bytes.
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
ASSERT_THAT(comms->RecvBytes(&buffer), IsTrue());
|
||||||
|
EXPECT_THAT(buffer.size(), Eq(1024 * 1024));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send 1M bytes.
|
||||||
|
std::vector<uint8_t> buffer(1024 * 1024);
|
||||||
|
memset(buffer.data(), 0, buffer.size());
|
||||||
|
ASSERT_THAT(comms->SendBytes(buffer), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvFD) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Receive FD and test it.
|
||||||
|
int fd = -1;
|
||||||
|
ASSERT_THAT(comms->RecvFD(&fd), IsTrue());
|
||||||
|
EXPECT_GE(fd, 0);
|
||||||
|
EXPECT_NE(fcntl(fd, F_GETFD), -1);
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send our STDERR to the thread.
|
||||||
|
ASSERT_THAT(comms->SendFD(STDERR_FILENO), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvEmptyTLV) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Receive TLV without a value.
|
||||||
|
uint32_t tag;
|
||||||
|
std::vector<uint8_t> value;
|
||||||
|
ASSERT_THAT(comms->RecvTLV(&tag, &value), IsTrue()); // NOLINT
|
||||||
|
EXPECT_THAT(tag, Eq(0x00DEADBE));
|
||||||
|
EXPECT_THAT(value.size(), Eq(0));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send TLV without a value.
|
||||||
|
ASSERT_THAT(comms->SendTLV(0x00DEADBE, 0, nullptr), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvEmptyTLV2) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Receive TLV without a value.
|
||||||
|
uint32_t tag;
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
ASSERT_THAT(comms->RecvTLV(&tag, &data), IsTrue());
|
||||||
|
EXPECT_THAT(tag, Eq(0x00DEADBE));
|
||||||
|
EXPECT_THAT(data.size(), Eq(0));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send TLV without a value.
|
||||||
|
ASSERT_THAT(comms->SendTLV(0x00DEADBE, 0, nullptr), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvProto) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Receive a ProtoBuf.
|
||||||
|
std::unique_ptr<CommsTestMsg> comms_msg(new CommsTestMsg());
|
||||||
|
ASSERT_THAT(comms->RecvProtoBuf(comms_msg.get()), IsTrue());
|
||||||
|
std::string tmp_str;
|
||||||
|
ASSERT_THAT(google::protobuf::TextFormat::PrintToString(*comms_msg, &tmp_str),
|
||||||
|
IsTrue());
|
||||||
|
EXPECT_THAT(tmp_str, Eq(kProtoStr));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send a ProtoBuf.
|
||||||
|
std::unique_ptr<CommsTestMsg> comms_msg(new CommsTestMsg());
|
||||||
|
ASSERT_THAT(google::protobuf::TextFormat::ParseFromString(kProtoStr, comms_msg.get()),
|
||||||
|
IsTrue());
|
||||||
|
ASSERT_THAT(comms->SendProtoBuf(*comms_msg), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvStatusOK) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Receive a good status.
|
||||||
|
sapi::Status status;
|
||||||
|
ASSERT_THAT(comms->RecvStatus(&status), IsTrue());
|
||||||
|
EXPECT_THAT(status, IsOk());
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send a good status.
|
||||||
|
ASSERT_THAT(comms->SendStatus(sapi::OkStatus()), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvStatusFailing) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Receive a failing status.
|
||||||
|
sapi::Status status;
|
||||||
|
ASSERT_THAT(comms->RecvStatus(&status), IsTrue());
|
||||||
|
EXPECT_THAT(status, Not(IsOk()));
|
||||||
|
EXPECT_THAT(status, StatusIs(sapi::StatusCode::kInternal, "something odd"));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send a failing status.
|
||||||
|
ASSERT_THAT(comms->SendStatus(
|
||||||
|
sapi::Status{sapi::StatusCode::kInternal, "something odd"}),
|
||||||
|
IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestUsesDistinctBuffers) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Receive 1M bytes.
|
||||||
|
std::vector<uint8_t> buffer1, buffer2;
|
||||||
|
ASSERT_THAT(comms->RecvBytes(&buffer1), IsTrue()); // NOLINT
|
||||||
|
EXPECT_THAT(buffer1.size(), Eq(1024 * 1024));
|
||||||
|
|
||||||
|
ASSERT_THAT(comms->RecvBytes(&buffer2), IsTrue()); // NOLINT
|
||||||
|
EXPECT_THAT(buffer2.size(), Eq(1024 * 1024));
|
||||||
|
|
||||||
|
// Make sure we can access the buffer (memory was not free'd).
|
||||||
|
// Probably only useful when running with ASAN/MSAN.
|
||||||
|
EXPECT_THAT(buffer1[1024 * 1024 - 1], Eq(buffer1[1024 * 1024 - 1]));
|
||||||
|
EXPECT_THAT(buffer2[1024 * 1024 - 1], Eq(buffer2[1024 * 1024 - 1]));
|
||||||
|
EXPECT_NE(buffer1.data(), buffer2.data());
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send 1M bytes.
|
||||||
|
absl::FixedArray<uint8_t> buf(1024 * 1024);
|
||||||
|
memset(buf.data(), 0, buf.size());
|
||||||
|
ASSERT_THAT(comms->SendBytes(buf.data(), buf.size()), IsTrue());
|
||||||
|
ASSERT_THAT(comms->SendBytes(buf.data(), buf.size()), IsTrue());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvCredentials) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Check credentials.
|
||||||
|
pid_t pid;
|
||||||
|
uid_t uid;
|
||||||
|
gid_t gid;
|
||||||
|
ASSERT_THAT(comms->RecvCreds(&pid, &uid, &gid), IsTrue());
|
||||||
|
EXPECT_THAT(pid, Eq(getpid()));
|
||||||
|
EXPECT_THAT(uid, Eq(getuid()));
|
||||||
|
EXPECT_THAT(gid, Eq(getgid()));
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Nothing to do here.
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendTooMuchData) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
// Nothing to do here.
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
// Send too much data.
|
||||||
|
ASSERT_THAT(comms->SendBytes(nullptr, comms->GetMaxMsgSize() + 1),
|
||||||
|
IsFalse());
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestSendRecvBytes) {
|
||||||
|
auto a = [](Comms* comms) {
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
ASSERT_THAT(comms->RecvBytes(&buffer), IsTrue());
|
||||||
|
ASSERT_THAT(comms->SendBytes(buffer), IsTrue());
|
||||||
|
};
|
||||||
|
auto b = [](Comms* comms) {
|
||||||
|
const std::vector<uint8_t> request = {0, 1, 2, 3, 7};
|
||||||
|
ASSERT_THAT(comms->SendBytes(request), IsTrue());
|
||||||
|
|
||||||
|
std::vector<uint8_t> response;
|
||||||
|
ASSERT_THAT(comms->RecvBytes(&response), IsTrue());
|
||||||
|
EXPECT_THAT(request, Eq(response));
|
||||||
|
};
|
||||||
|
HandleCommunication(sockname_, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SenderThread {
|
||||||
|
public:
|
||||||
|
SenderThread(Comms* comms, size_t rounds) : comms_(comms), rounds_(rounds) {}
|
||||||
|
void operator()() {
|
||||||
|
for (size_t i = 0; i < rounds_; i++) {
|
||||||
|
ASSERT_THAT(comms_->SendBytes(reinterpret_cast<const uint8_t*>("Test"), 4),
|
||||||
|
IsTrue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Comms* comms_;
|
||||||
|
size_t rounds_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReceiverThread {
|
||||||
|
public:
|
||||||
|
ReceiverThread(Comms* comms, size_t rounds)
|
||||||
|
: comms_(comms), rounds_(rounds) {}
|
||||||
|
void operator()() {
|
||||||
|
for (size_t i = 0; i < rounds_; i++) {
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
EXPECT_THAT(comms_->RecvBytes(&buffer), IsTrue());
|
||||||
|
EXPECT_THAT(buffer.size(), Eq(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Comms* comms_;
|
||||||
|
size_t rounds_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(CommsTest, TestMultipleThreads) {
|
||||||
|
// The comms object should be thread safe, this testcase covers this.
|
||||||
|
constexpr size_t kNumThreads = 20;
|
||||||
|
constexpr size_t kNumRoundsPerThread = 50;
|
||||||
|
constexpr size_t kNumRounds = kNumThreads * kNumRoundsPerThread;
|
||||||
|
Comms c(sockname_);
|
||||||
|
c.Listen();
|
||||||
|
|
||||||
|
// Start the client thread.
|
||||||
|
std::string socketname = sockname_;
|
||||||
|
std::thread ct([&socketname]() {
|
||||||
|
Comms comms(socketname);
|
||||||
|
CHECK(comms.Connect());
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
|
||||||
|
// Receive N_ROUND times. We keep the local buffer and send it back
|
||||||
|
// later to increase our A/MSAN coverage.
|
||||||
|
for (size_t i = 0; i < kNumRounds; i++) {
|
||||||
|
ASSERT_THAT(comms.RecvBytes(&buffer), IsTrue());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kNumRounds; i++) {
|
||||||
|
ASSERT_THAT(comms.SendBytes(buffer), IsTrue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Accept connection.
|
||||||
|
ASSERT_THAT(c.Accept(), IsTrue());
|
||||||
|
|
||||||
|
// Start sender threads.
|
||||||
|
{
|
||||||
|
std::thread sender_threads[kNumThreads];
|
||||||
|
for (size_t i = 0; i < kNumThreads; i++) {
|
||||||
|
sender_threads[i] = std::thread(SenderThread(&c, kNumRoundsPerThread));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join threads.
|
||||||
|
for (size_t i = 0; i < kNumThreads; i++) {
|
||||||
|
sender_threads[i].join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start receiver threads.
|
||||||
|
{
|
||||||
|
std::thread receiver_threads[kNumThreads];
|
||||||
|
for (size_t i = 0; i < kNumThreads; i++) {
|
||||||
|
receiver_threads[i] =
|
||||||
|
std::thread(ReceiverThread(&c, kNumRoundsPerThread));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join threads.
|
||||||
|
for (size_t i = 0; i < kNumThreads; i++) {
|
||||||
|
receiver_threads[i].join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ct.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot test this in the Client or Server tests, as the endpoint needs to
|
||||||
|
// be unconnected.
|
||||||
|
TEST_F(CommsTest, TestMsgSize) {
|
||||||
|
// There will be no actual connection to this socket.
|
||||||
|
const std::string socket_name = "sandbox2_comms_msg_size_test";
|
||||||
|
Comms c(socket_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
22
sandboxed_api/sandbox2/comms_test.proto
Normal file
22
sandboxed_api/sandbox2/comms_test.proto
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// A proto for comms_test
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
package sandbox2;
|
||||||
|
|
||||||
|
message CommsTestMsg {
|
||||||
|
repeated string value = 1;
|
||||||
|
};
|
148
sandboxed_api/sandbox2/deathrattle_fatalmsg.proto
Normal file
148
sandboxed_api/sandbox2/deathrattle_fatalmsg.proto
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package sandbox2;
|
||||||
|
|
||||||
|
import "sandboxed_api/sandbox2/mounttree.proto";
|
||||||
|
|
||||||
|
enum PBViolationType {
|
||||||
|
VIOLATION_TYPE_UNSPECIFIED = 0;
|
||||||
|
DISALLOWED_SYSCALL = 1;
|
||||||
|
RESOURCE_LIMIT_EXCEEDED = 2;
|
||||||
|
SYSCALL_ARCHITECTURE_MISMATCH = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// X86_64 not allowed (naming convention...)
|
||||||
|
message RegisterX8664 {
|
||||||
|
uint64 r15 = 1;
|
||||||
|
uint64 r14 = 2;
|
||||||
|
uint64 r13 = 3;
|
||||||
|
uint64 r12 = 4;
|
||||||
|
uint64 rbp = 5;
|
||||||
|
uint64 rbx = 6;
|
||||||
|
uint64 r11 = 7;
|
||||||
|
uint64 r10 = 8;
|
||||||
|
uint64 r9 = 9;
|
||||||
|
uint64 r8 = 10;
|
||||||
|
uint64 rax = 11;
|
||||||
|
uint64 rcx = 12;
|
||||||
|
uint64 rdx = 13;
|
||||||
|
uint64 rsi = 14;
|
||||||
|
uint64 rdi = 15;
|
||||||
|
uint64 orig_rax = 16;
|
||||||
|
uint64 rip = 17;
|
||||||
|
uint64 cs = 18;
|
||||||
|
uint64 eflags = 19;
|
||||||
|
uint64 rsp = 20;
|
||||||
|
uint64 ss = 21;
|
||||||
|
uint64 fs_base = 22;
|
||||||
|
uint64 gs_base = 23;
|
||||||
|
uint64 ds = 24;
|
||||||
|
uint64 es = 25;
|
||||||
|
uint64 fs = 26;
|
||||||
|
uint64 gs = 27;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisterPowerpc64 {
|
||||||
|
repeated uint64 gpr = 1;
|
||||||
|
uint64 nip = 2;
|
||||||
|
uint64 msr = 3;
|
||||||
|
uint64 orig_gpr3 = 4;
|
||||||
|
uint64 ctr = 5;
|
||||||
|
uint64 link = 6;
|
||||||
|
uint64 xer = 7;
|
||||||
|
uint64 ccr = 8;
|
||||||
|
uint64 softe = 9;
|
||||||
|
uint64 trap = 10;
|
||||||
|
uint64 dar = 11;
|
||||||
|
uint64 dsisr = 12;
|
||||||
|
uint64 result = 13;
|
||||||
|
|
||||||
|
uint64 zero0 = 14;
|
||||||
|
uint64 zero1 = 15;
|
||||||
|
uint64 zero2 = 16;
|
||||||
|
uint64 zero3 = 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated.
|
||||||
|
message RegisterAarch64 {
|
||||||
|
repeated uint64 regs = 1;
|
||||||
|
uint64 sp = 2;
|
||||||
|
uint64 pc = 3;
|
||||||
|
uint64 pstate = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisterValues {
|
||||||
|
// Architecture architecture = 1;
|
||||||
|
oneof register_values {
|
||||||
|
RegisterX8664 register_x86_64 = 2;
|
||||||
|
RegisterPowerpc64 register_powerpc64 = 3;
|
||||||
|
RegisterAarch64 register_aarch64 = 4; // Deprecated.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message SyscallDescription {
|
||||||
|
int32 syscall = 1;
|
||||||
|
// Should we have a second one with the raw value?
|
||||||
|
// This would be redundant (We dump all registers) + should not be as useful
|
||||||
|
// for debugging as the decoded values.
|
||||||
|
repeated string argument = 2;
|
||||||
|
// Store the architecture of the desired syscall in here as well? Might be
|
||||||
|
// useful when the violation type was a change in syscall architecture.
|
||||||
|
}
|
||||||
|
|
||||||
|
message FsDescription {
|
||||||
|
repeated string file_whitelist = 1;
|
||||||
|
repeated string symlink_whitelist = 2;
|
||||||
|
repeated string file_greylist = 3;
|
||||||
|
repeated string file_blacklist = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PolicyBuilderDescription {
|
||||||
|
repeated int32 handled_syscalls = 1;
|
||||||
|
repeated string bind_mounts = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NamespaceDescription {
|
||||||
|
int32 clone_flags = 1;
|
||||||
|
// Do we want to have the mount tree in here?
|
||||||
|
MountTree mount_tree_mounts = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PolicyDescription {
|
||||||
|
bytes user_bpf_policy = 1;
|
||||||
|
reserved 2 to 5;
|
||||||
|
// This requires additional fields. (e.g. whitelisted syscall #s)
|
||||||
|
PolicyBuilderDescription policy_builder_description = 6;
|
||||||
|
|
||||||
|
// namespace
|
||||||
|
NamespaceDescription namespace_description = 7;
|
||||||
|
|
||||||
|
repeated int32 capabilities = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Violation {
|
||||||
|
string legacy_fatal_message = 1;
|
||||||
|
PBViolationType violation_type = 2;
|
||||||
|
int32 pid = 3;
|
||||||
|
string prog_name = 4;
|
||||||
|
PolicyDescription policy = 5;
|
||||||
|
string stack_trace = 6;
|
||||||
|
SyscallDescription syscall_information = 7;
|
||||||
|
RegisterValues register_values = 8;
|
||||||
|
FsDescription fs = 9;
|
||||||
|
string proc_maps = 10;
|
||||||
|
}
|
119
sandboxed_api/sandbox2/docs/examples.md
Normal file
119
sandboxed_api/sandbox2/docs/examples.md
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
We have prepared a few examples to demonstrate how to use sandbox2 depending on
|
||||||
|
your situation and how to write policies.
|
||||||
|
|
||||||
|
You can find them in [//sandboxed_api/sandbox2/examples](../examples), read on
|
||||||
|
for detailed explanations.
|
||||||
|
|
||||||
|
## CRC4
|
||||||
|
|
||||||
|
The CRC4 example is an intentionally buggy calculation of a CRC4 checksum, it
|
||||||
|
demonstrates how to sandbox another program and how to communicate with it.
|
||||||
|
|
||||||
|
* [crc4bin.cc](../examples/crc4/crc4bin.cc): is the program we want to sandbox
|
||||||
|
(the *sandboxee*)
|
||||||
|
* [crc4sandbox.cc](../examples/crc4/crc4sandbox.cc): is the sandbox program that
|
||||||
|
will run it (the *executor*).
|
||||||
|
|
||||||
|
How it works:
|
||||||
|
1. The *executor* starts the *sandboxee* from its file path using
|
||||||
|
`::sandbox2::GetDataDependencyFilePath()`.
|
||||||
|
2. The *executor* sends input to the *sandboxee* over the communication channel
|
||||||
|
`Comms` using `SendBytes()`.
|
||||||
|
3. The *sandboxee* calculates the CRC4 and sends its replies back to the
|
||||||
|
*executor* over the communication channel `Comms` which receives it with
|
||||||
|
`RecvUint32()`.
|
||||||
|
|
||||||
|
If the program makes any other syscall other than communicating (`read()` and
|
||||||
|
`write()`), it is killed for policy violation.
|
||||||
|
|
||||||
|
|
||||||
|
## static
|
||||||
|
|
||||||
|
The static example demonstrates how to sandbox a statically linked binary, such
|
||||||
|
as a third-party binary for which you do not have the source, so is not aware
|
||||||
|
that it will be sandboxed.
|
||||||
|
|
||||||
|
* [static_bin.cc](../examples/static/static_bin.cc): the *sandboxee* is a
|
||||||
|
static C binary that converts ASCII text from standard input to uppercase.
|
||||||
|
* [static_sandbox.cc](../examples/static/static_sandbox.cc): the *executor*
|
||||||
|
with its policy, limits and using a file descriptor for *sandboxee* input.
|
||||||
|
|
||||||
|
How it works:
|
||||||
|
|
||||||
|
1. The *executor* starts the *sandboxee* from its file path using
|
||||||
|
`GetDataDependencyFilepath`, just like for **CRC4**.
|
||||||
|
2. It sets up limits, opens a file descriptor on `/proc/version` and marks it
|
||||||
|
to be mapped in the *sandboxee* with `MapFd`.
|
||||||
|
3. The policy allows some syscalls (`open`) to return an error (`ENOENT`),
|
||||||
|
rather than being killed for policy violation. This can be useful when
|
||||||
|
sandboxing a third party program where we cannot modify which syscalls are
|
||||||
|
made, but we can make them fail gracefully.
|
||||||
|
|
||||||
|
## tool
|
||||||
|
|
||||||
|
The tool example is both a tool to develop your own policies and experiment with
|
||||||
|
**sandbox2** APIs as well a demonstration of its features.
|
||||||
|
|
||||||
|
* [sandbox2tool.cc](..examples/tool/sandbox2tool.cc): the *executor*
|
||||||
|
demonstrating
|
||||||
|
* how to run another binary sandboxed,
|
||||||
|
* how to set up filesystem checks, and
|
||||||
|
* how the *executor* can run the *sandboxee* asynchronously to read its
|
||||||
|
output progressively
|
||||||
|
|
||||||
|
Try it yourself:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bazel run //sandboxed_api/sandbox2/examples/tool:sandbox2tool -- \
|
||||||
|
/bin/cat /etc/hostname
|
||||||
|
```
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
|
||||||
|
* `--sandbox2tool_keep_env` to keep current environment variables
|
||||||
|
* `--sandbox2tool_redirect_fd1` to receive the *sandboxee* STDOUT_FILENO (1)
|
||||||
|
and output it locally
|
||||||
|
* `--sandbox2tool_cpu_timeout` to set CPU timeout in seconds
|
||||||
|
* `--sandbox2tool_walltime_timeout` to set wall-time timeout in seconds
|
||||||
|
* `--sandbox2tool_file_size_creation_limit` to set the maximum size of created
|
||||||
|
files
|
||||||
|
* `--sandbox2tool_cwd` to set sandbox current working directory
|
||||||
|
|
||||||
|
## custom_fork
|
||||||
|
|
||||||
|
The custom_fork example demonstrates how to create a sandbox, which will
|
||||||
|
initialize the binary, and then wait for `fork()` requests coming from the
|
||||||
|
parent executor.
|
||||||
|
|
||||||
|
This mode offers potentially increased performance with regard to other types of
|
||||||
|
sandboxing, as here, creating new instances of sandboxees doesn't require
|
||||||
|
executing new binaries, just fork()-ing the existing ones
|
||||||
|
|
||||||
|
* [custom_fork_bin.cc](../examples/custom_fork): is the custom fork-server,
|
||||||
|
receiving requests to `fork()` (via `Client::WaitAndFork`) in order to spawn
|
||||||
|
new sandboxees
|
||||||
|
* [custom_fork_sandbox.cc](../examples/custom_fork/custom_fork_sandbox.cc): is
|
||||||
|
the executor, which starts a custom fork server. Then it sends requests to it
|
||||||
|
(via new executors) to spawn (via `fork()`) new sandboxees.
|
||||||
|
|
||||||
|
## network
|
||||||
|
|
||||||
|
Enabling the network namespace prevents the sandboxed process from connecting to
|
||||||
|
the outside world. This example demonstrates how to deal with this problem.
|
||||||
|
|
||||||
|
Namespaces are enabled when either
|
||||||
|
`::sandbox2::PolicyBuilder::EnableNamespaces()` is called, or some other
|
||||||
|
function that enables namespaces like `AddFile()`. To deal with this problem,
|
||||||
|
we can initialize a connection inside the executor and pass the socket file
|
||||||
|
descriptor via `::sandbox2::Comms::SendFD()`. The sandboxee receives the socket
|
||||||
|
by using `::sandbox2::Comms::RecvFD()` and then it can use this socket to
|
||||||
|
exchange the data as usual.
|
||||||
|
|
||||||
|
* [network_bin.cc](examples/network/network_bin.cc): is the program we want to
|
||||||
|
sandbox (the sandboxee).
|
||||||
|
* [network_sandbox.cc](examples/network/network_sandbox.cc): is the sandbox
|
||||||
|
program that will run it (the executor).
|
123
sandboxed_api/sandbox2/docs/faq.md
Normal file
123
sandboxed_api/sandbox2/docs/faq.md
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
## Can I use threads?
|
||||||
|
|
||||||
|
Yes, threads are supported in sandbox2.
|
||||||
|
|
||||||
|
### All threads must be sandboxed
|
||||||
|
|
||||||
|
Because of the way Linux works, the seccomp-bpf policy is applied to the current
|
||||||
|
thread only: this means other existing threads do not get the policy, but future
|
||||||
|
threads will inherit the policy.
|
||||||
|
|
||||||
|
If you are using sandbox2 in the
|
||||||
|
[default mode](getstarted.md#a-Execute-a-binary-with-sandboxing-already-enabled)
|
||||||
|
where sandboxing is enabled before `execve()`, all threads will inherit the
|
||||||
|
policy, and there is no problem. This is the preferred mode of sandboxing.
|
||||||
|
|
||||||
|
If you want to use the
|
||||||
|
[second mode](getstarted.md#b-Tell-the-executor-when-to-be-sandboxed) where the
|
||||||
|
executor has
|
||||||
|
`set_enable_sandbox_before_exec(false)` and the sandboxee tells the executor
|
||||||
|
when it wants to be sandboxed with `SandboxMeHere()`, then the filter still
|
||||||
|
needs to be applied to all threads. Otherwise, there is a risk of a sandbox
|
||||||
|
escape: malicious code could migrate from a sandboxed thread to an unsandboxed
|
||||||
|
thread.
|
||||||
|
|
||||||
|
The Linux kernel introduced the TSYNC flag in version 3.17, which allows
|
||||||
|
applying a policy to all threads. Before this flag, it was only possible to
|
||||||
|
apply the policy on a thread-by-thread basis.
|
||||||
|
|
||||||
|
If sandbox2 detects that it is running on a kernel without TSYNC-support and you
|
||||||
|
call `SandboxMeHere()` from multi-threaded program, sandbox2 will abort, since
|
||||||
|
this would compromise the safety of the sandbox.
|
||||||
|
|
||||||
|
## How should I compile my sandboxee?
|
||||||
|
|
||||||
|
If not careful, it is easy to inherit a lot of dependencies and side effects
|
||||||
|
(extra syscalls, file accesses or even network connections) which make
|
||||||
|
sandboxing harder (tracking down all side effects) and less safe (because the
|
||||||
|
syscall and file policies are wider). Some compile options can help reduce this:
|
||||||
|
|
||||||
|
* statically compile the sandboxee binary to avoid dynamic linking which uses a
|
||||||
|
lot of syscalls (`open()`/`openat()`, `mmap()`, etc.). Also since Bazel adds
|
||||||
|
`pie` by default but static is incompatible with it, use the features flag to
|
||||||
|
force it off.
|
||||||
|
That is, use the following options in
|
||||||
|
[cc_binary](https://docs.bazel.build/versions/master/be/c-cpp.html#cc_binary)
|
||||||
|
rules:
|
||||||
|
|
||||||
|
```python
|
||||||
|
linkstatic = 1,
|
||||||
|
features = [
|
||||||
|
"fully_static_link", # link libc statically
|
||||||
|
"-pie",
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
*However:* this has the downside of reducing ASLR heap entropy (from 30 bits
|
||||||
|
to 8 bits), making exploits easier. Decide carefully what is preferable
|
||||||
|
depending on your sandbox implementation and policy:
|
||||||
|
|
||||||
|
* **not static**: good heap ASLR, potentially harder to get initial code
|
||||||
|
execution but at the cost of a less effective sandbox policy, potentially
|
||||||
|
easier to break out of.
|
||||||
|
* **static**: bad heap ASLR, potentially easier to get initial code execution
|
||||||
|
but a more effective sandbox policy, potentially harder to break out of.
|
||||||
|
|
||||||
|
It is an unfortunate choice to make because the compiler does not support
|
||||||
|
static PIE (Position Independent Executables). PIE is implemented by having
|
||||||
|
the binary be a dynamic object, and the dynamic loader maps it at a random
|
||||||
|
location before executing it. Then because the heap is traditionnally placed
|
||||||
|
at a random offset after the base address of the binary (and expanded with
|
||||||
|
`brk` syscall), it means for static binaries the heap ASLR entropy is only
|
||||||
|
this offset because there is no PIE.
|
||||||
|
|
||||||
|
For examples of these compiling options, look at the
|
||||||
|
[static](examples.md#static) example
|
||||||
|
[BUILD.bazel](../examples/static/BUILD.bazel): `static_bin.cc` is compiled
|
||||||
|
statically, which allows us to have a very tight syscall policy. This works
|
||||||
|
nicely for sandboxing third party binaries too.
|
||||||
|
|
||||||
|
## Can I sandbox 32-bit x86 binaries?
|
||||||
|
|
||||||
|
Sandbox2 can only sandbox the same arch as it was compiled with.
|
||||||
|
|
||||||
|
In addition, support for 32-bit x86 has been removed from Sandbox2. If you try
|
||||||
|
to use a 64-bit x86 executor to sandbox a 32-bit x86 binary, or a 64-bit x86
|
||||||
|
binary making 32-bit syscalls (via `int 0x80`), both will generate a sandbox
|
||||||
|
violation that can be identified with the architecture label *[X86-32]*.
|
||||||
|
|
||||||
|
The reason behind this behavior is that syscall numbers are different between
|
||||||
|
architectures and since the syscall policy is written in the architecture of the
|
||||||
|
executor, it would be dangerous to allow a different architecture for the
|
||||||
|
sandboxee. Indeed, allowing an seemingly harmless syscall that in fact means
|
||||||
|
another more harmful syscall could open up the sandbox to an escape.
|
||||||
|
|
||||||
|
## Any limits on the number of sandboxes an executor process can request?
|
||||||
|
|
||||||
|
For each sandboxee instance (new process spawned from the forkserver) a new
|
||||||
|
thread is created - that's where the limitation would lie.
|
||||||
|
|
||||||
|
## Can an Executor request the creation of more than one Sandbox?
|
||||||
|
|
||||||
|
No. There is a 1:1 correspondence - an `Executor` instance stores the PID of the
|
||||||
|
sandboxee, manages the `Comms` instance to the `Sandbox` instance, etc.
|
||||||
|
|
||||||
|
## Can I use sandbox2 from Go?
|
||||||
|
|
||||||
|
Yes. Write your executor in C++ and expose it to Go via SWIG.
|
||||||
|
|
||||||
|
## Why do I get `Function not implemented` inside `forkserver.cc?`
|
||||||
|
|
||||||
|
Sandbox2 only supports running on reasonably new kernels. Our current cut-off is
|
||||||
|
the 3.19 kernel though that might change in the future. The reason for this is
|
||||||
|
that we are using relatively new kernel features including user namespaces and
|
||||||
|
seccomp with the TSYNC flag.
|
||||||
|
|
||||||
|
If you are running on prod, this should not be in issue, since almost the entire
|
||||||
|
fleet is running a new enough kernel. If you have any issues with this, please
|
||||||
|
contact us.
|
||||||
|
|
||||||
|
If you are running on Debian or Ubuntu, updating your kernel is as easy as
|
||||||
|
`apt-get install linux-image-[recent version]`.
|
356
sandboxed_api/sandbox2/docs/getting-started.md
Normal file
356
sandboxed_api/sandbox2/docs/getting-started.md
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
# Getting started with Sandbox2
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
In this guide, you will learn how to create your own sandbox, policy and tweaks.
|
||||||
|
It is meant as a guide, alongside the [examples](examples.md) and code
|
||||||
|
documentation in the header files.
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Choose an executor
|
||||||
|
|
||||||
|
Sandboxing starts with an *executor* (see [How it works](howitworks.md)), which
|
||||||
|
will be responsible for running the *sandboxee*. The API for this is in
|
||||||
|
[executor.h](../executor.h). It is very flexible to let you choose what works
|
||||||
|
best for your use case.
|
||||||
|
|
||||||
|
### a. Execute a binary with sandboxing already enabled
|
||||||
|
|
||||||
|
This is the simplest and safest way to use sandboxing. For examples see
|
||||||
|
[static](examples.md#static) and [sandboxed tool](examples.md#tool).
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
|
||||||
|
std::string path = "path/to/binary";
|
||||||
|
std::vector<string> args = {path}; // args[0] will become the sandboxed
|
||||||
|
// process' argv[0], typically the path
|
||||||
|
// to the binary.
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(path, args);
|
||||||
|
```
|
||||||
|
|
||||||
|
### b. Tell the executor when to be sandboxed
|
||||||
|
|
||||||
|
This offers you the flexibility to be unsandboxed during initialization, then
|
||||||
|
choose when to enter sandboxing by calling
|
||||||
|
`::sandbox2::Client::SandboxMeHere()`. The code has to be careful to always
|
||||||
|
call this or it would be unsafe to proceed, and it has to be single-threaded
|
||||||
|
(read why in the [FAQ](faq.md#Can-I-use-threads)). For an example see
|
||||||
|
[crc4](examples.md#CRC4).
|
||||||
|
|
||||||
|
Note: The [filesystem restrictions](#Filesystem-checks) will be in effect right
|
||||||
|
from the start of your sandboxee. Using this mode allows you to enable the
|
||||||
|
syscall filter later on from the sandboxee.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
|
||||||
|
std::string path = "path/to/binary";
|
||||||
|
std::vector<std::string> args = {path};
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(path, args);
|
||||||
|
executor->set_enable_sandbox_before_exec(false);
|
||||||
|
```
|
||||||
|
|
||||||
|
### c. Prepare a binary, wait for fork requests, and sandbox on your own
|
||||||
|
|
||||||
|
This mode allows you to start a binary, prepare it for sandboxing, and - at the
|
||||||
|
specific moment of your binary's lifecycle - make it available for the
|
||||||
|
executor. The executor will send fork request to your binary, which will
|
||||||
|
`fork()` (via `::sandbox2::ForkingClient::WaitAndFork()`). The newly created
|
||||||
|
process will be ready to be sandboxed with
|
||||||
|
`::sandbox2::Client::SandboxMeHere()`. This mode comes with a few downsides,
|
||||||
|
however: For example, it pulls in more dependencies in your sandboxee and
|
||||||
|
does not play well with namespaces, so it is only recommended it if you have
|
||||||
|
tight performance requirements.
|
||||||
|
|
||||||
|
For an example see [custom_fork](examples.md#custom_fork).
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
|
||||||
|
// Start the custom ForkServer
|
||||||
|
std::string path = "path/to/binary";
|
||||||
|
std::vector<std::string> args = {path};
|
||||||
|
auto fork_executor = absl::make_unique<sandbox2::Executor>(path, args);
|
||||||
|
fork_executor->StartForkServer();
|
||||||
|
|
||||||
|
// Initialize Executor with Comms channel to the ForkServer
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(
|
||||||
|
fork_executor->ipc()->GetComms());
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Creating a policy
|
||||||
|
|
||||||
|
Once you have an executor you need to define the policy for the sandboxee: this
|
||||||
|
will restrict the syscalls and arguments that the sandboxee can make as well as
|
||||||
|
the files it can access. For instance, a policy could allow `read()` on a given
|
||||||
|
file descriptor (e.g. `0` for stdin) but not another.
|
||||||
|
|
||||||
|
To create a [policy object][filter], use the
|
||||||
|
[PolicyBuilder](../policybuilder.h). It comes with helper functions that allow
|
||||||
|
many common operations (such as `AllowSystemMalloc()`), whitelist syscalls
|
||||||
|
(`AllowSyscall()`) or grant access to files (`AddFile()`).
|
||||||
|
|
||||||
|
If you want to restrict syscall arguments or need to perform more complicated
|
||||||
|
checks, you can specify a raw seccomp-bpf filter using the bpf helper macros
|
||||||
|
from the Linux kernel. See the [kernel documentation][filter] for more
|
||||||
|
information about BPF. If you find yourself writing repetitive BPF-code that
|
||||||
|
you think should have a usability-wrapper, feel free to file a feature request.
|
||||||
|
|
||||||
|
Coming up with the syscalls to whitelist is still a bit of manual work
|
||||||
|
unfortunately. Create a policy with the syscalls you know your binary needs and
|
||||||
|
run it with a common workload. If a violation gets triggered, whitelist the
|
||||||
|
syscall and repeat the process. If you run into a violation that you think might
|
||||||
|
be risky to whitelist and the program handles errors gracefullly, you can try to
|
||||||
|
make it return an error instead with `BlockSyscallWithErrno()`.
|
||||||
|
|
||||||
|
[filter]: https://www.kernel.org/doc/Documentation/networking/filter.txt
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
|
||||||
|
|
||||||
|
std::unique_ptr<sandbox2::Policy> CreatePolicy() {
|
||||||
|
return sandbox2::PolicyBuilder()
|
||||||
|
.AllowSyscall(__NR_read) // See also AllowRead()
|
||||||
|
.AllowTime() // Allow time, gettimeofday and clock_gettime
|
||||||
|
.AddPolicyOnSyscall(__NR_write, {
|
||||||
|
ARG(0), // fd is the first argument of write (argument #0)
|
||||||
|
JEQ(1, ALLOW), // allow write only on fd 1
|
||||||
|
KILL, // kill if not fd 1
|
||||||
|
})
|
||||||
|
.AddPolicyOnSyscall(__NR_mprotect, {
|
||||||
|
ARG_32(2), // prot is a 32-bit wide argument, so it's OK to use *_32
|
||||||
|
// macro here
|
||||||
|
JNE32(PROT_READ | PROT_WRITE, KILL), // prot must be the RW, otherwise
|
||||||
|
// kill the process
|
||||||
|
ARG(1), // len is a 64-bit argument
|
||||||
|
JNE(0x1000, KILL), // Allow single page syscalls only, otherwise kill
|
||||||
|
// the process
|
||||||
|
ALLOW, // Allow for the syscall to proceed, if prot and
|
||||||
|
// size match
|
||||||
|
})
|
||||||
|
// Allow the open() syscall but always return "not found".
|
||||||
|
.BlockSyscallWithErrno(__NR_open, ENOENT)
|
||||||
|
.BuildOrDie();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Tip: Test for the most used syscalls at the beginning so you can allow them
|
||||||
|
early without consulting the rest of the policy.
|
||||||
|
|
||||||
|
|
||||||
|
### Filesystem checks
|
||||||
|
|
||||||
|
The default way to grant access to files is by using the `AddFile()` class of
|
||||||
|
functions of the `PolicyBuilder`. This will automatically enable user namespace
|
||||||
|
support that allows us to create a custom chroot for the sandboxee and gives you
|
||||||
|
some other features such as creating tmpfs mounts.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
sandbox2::PolicyBuilder()
|
||||||
|
// ...
|
||||||
|
.AddFile("/etc/localtime")
|
||||||
|
.AddDirectory("/usr/share/fonts")
|
||||||
|
.AddTmpfs("/tmp")
|
||||||
|
.BuildOrDie();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Adjusting limits
|
||||||
|
|
||||||
|
Sandboxing by restricting syscalls is one thing, but if the job can run
|
||||||
|
indefinitely or exhaust RAM and other resources that is not good either.
|
||||||
|
Therefore, by default the sandboxee runs under tight execution limits, which can
|
||||||
|
be adjusted using the [Limits](../limits.h) class, available by calling
|
||||||
|
`limits()` on the `Executor` object created earlier. For an example see [sandbox
|
||||||
|
tool](examples.md#tool).
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// Restrict the address space size of the sandboxee to 4 GiB.
|
||||||
|
executor->limits()->set_rLimit_as(4ULL << 30);
|
||||||
|
// Kill sandboxee with SIGXFSZ if it writes more than 1 GiB to the filesystem.
|
||||||
|
executor->limits()->set_rLimit_fsize(1ULL << 30);
|
||||||
|
// Number of file descriptors which can be used by the sandboxee.
|
||||||
|
executor->limits()->set_rLimit_nofile(1ULL << 10);
|
||||||
|
// The sandboxee is not allowed to create core files.
|
||||||
|
executor->limits()->set_rLimit_core(0);
|
||||||
|
// Maximum 300s of real CPU time.
|
||||||
|
executor->limits()->set_rLimit_cpu(300);
|
||||||
|
// Maximum 120s of wall time.
|
||||||
|
executor->limits()->set_walltime_limit(absl::Seconds(120));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Running the sandboxee
|
||||||
|
|
||||||
|
With our executor and policy ready, we can now create the `Sandbox2` object and
|
||||||
|
run it synchronously. For an example see [static](examples.md#static).
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
|
||||||
|
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
auto result = s2.Run(); // Synchronous
|
||||||
|
LOG(INFO) << "Result of sandbox execution: " << result.ToString();
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run it asynchronously, for instance to communicate with the
|
||||||
|
sandboxee. For examples see [crc4](examples.md#CRC4) and [sandbox
|
||||||
|
tool](examples.md#tool).
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
|
||||||
|
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
if (s2.RunAsync()) {
|
||||||
|
... // Communicate with sandboxee, use s2.Kill() to kill it if needed
|
||||||
|
}
|
||||||
|
auto result = s2.AwaitResult();
|
||||||
|
LOG(INFO) << "Final execution status: " << result.ToString();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Communicating with the sandboxee
|
||||||
|
|
||||||
|
The executor can communicate with the sandboxee with file descriptors.
|
||||||
|
|
||||||
|
Depending on your situation, that can be all that you need (e.g., to share a
|
||||||
|
file with the sandboxee or to read the sandboxee standard output).
|
||||||
|
|
||||||
|
If you need more communication logic, you can implement your own protocol or
|
||||||
|
reuse our convenient **comms** API able to send integers, strings, byte
|
||||||
|
buffers, protobufs or file descriptors. Bonus: in addition to C++, we also
|
||||||
|
provide a pure-C comms library, so it can be used easily when sandboxing C
|
||||||
|
third-party projects.
|
||||||
|
|
||||||
|
### a. Sharing file descriptors
|
||||||
|
|
||||||
|
Using the [IPC](../ipc.h) (*Inter-Process Communication*) API, you can either:
|
||||||
|
|
||||||
|
* use `MapFd()` to map file descriptors from the executor to the sandboxee, for
|
||||||
|
instance to share a file opened from the executor for use in the sandboxee,
|
||||||
|
as it is done in the [static](examples.md#static) example.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// The executor opened /proc/version and passes it to the sandboxee as stdin
|
||||||
|
executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
|
||||||
|
```
|
||||||
|
or
|
||||||
|
|
||||||
|
* use `ReceiveFd()` to create a socketpair endpoint, for instance to read the
|
||||||
|
sandboxee standard output or standard error, as it is done in the
|
||||||
|
[sandbox tool](examples.md#tool) example.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// The executor receives a file descriptor of the sandboxee stdout
|
||||||
|
int recv_fd1 = executor->ipc())->ReceiveFd(STDOUT_FILENO);
|
||||||
|
```
|
||||||
|
|
||||||
|
### b. Using the comms API
|
||||||
|
|
||||||
|
Using the [comms](../comms.h) API, you can send integers, strings or byte
|
||||||
|
buffers. For an example see [crc4](examples.md#CRC4).
|
||||||
|
|
||||||
|
To use comms, first get it from the executor IPC:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto* comms = executor->ipc()->GetComms();
|
||||||
|
```
|
||||||
|
|
||||||
|
To send data to the sandboxee, use one of the `Send*` family of functions.
|
||||||
|
For instance in the case of [crc4](examples.md#CRC4), the executor sends an
|
||||||
|
`unsigned char buf[size]` with `SendBytes(buf, size)`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
if (!(comms->SendBytes(static_cast<const uint8_t*>(buf), sz))) {
|
||||||
|
/* handle error */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To receive data from the sandboxee, use one of the `Recv*` functions. For
|
||||||
|
instance in the case of [crc4](examples.md#CRC4), the executor receives the
|
||||||
|
checksum into an 32-bit unsigned integer:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
uint32_t crc4;
|
||||||
|
if (!(comms->RecvUint32(&crc4))) {
|
||||||
|
/* handle error */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### c. Sharing data with buffers
|
||||||
|
|
||||||
|
In some situations, it can be useful to share data between executor and
|
||||||
|
sandboxee in order to share large amounts of data and to avoid expensive copies
|
||||||
|
that are sent back and forth. The [buffer API](../buffer.h) serves this use
|
||||||
|
case: the executor creates a `Buffer`, either by size and data to be passed, or
|
||||||
|
directly from a file descriptor, and passes it to the sandboxee using
|
||||||
|
`comms->SendFD()` in the executor and `comms->RecvFD()` in the sandboxee.
|
||||||
|
|
||||||
|
For example, to create a buffer in the executor, send its file descriptor to
|
||||||
|
the sandboxee, and afterwards see what the sandboxee did with it:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
sandbox2::Buffer buffer;
|
||||||
|
buffer.Create(1ULL << 20); // 1 MiB
|
||||||
|
s2.RunAsync();
|
||||||
|
comms->SendFD(buffer.GetFD());
|
||||||
|
auto result = s2.AwaitResult();
|
||||||
|
uint8* buf = buffer.buffer(); // As modified by sandboxee
|
||||||
|
size_t len = buffer.size();
|
||||||
|
```
|
||||||
|
|
||||||
|
On the other side the sandboxee receives the buffer file descriptor, creates the
|
||||||
|
buffer object and can work with it:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
int fd;
|
||||||
|
comms.RecvFD(&fd);
|
||||||
|
sandbox2::Buffer buffer;
|
||||||
|
buffer.Setup(fd);
|
||||||
|
uint8 *buf = buffer.GetBuffer();
|
||||||
|
memset(buf, 'X', buffer.GetSize()); /* work with the buffer */
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Exiting
|
||||||
|
|
||||||
|
If running the sandbox synchronously, then `Run` will only return when it's
|
||||||
|
finished:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto result = s2.Run();
|
||||||
|
LOG(INFO) << "Final execution status: " << result.ToString();
|
||||||
|
```
|
||||||
|
|
||||||
|
If running asynchronously, you can decide at anytime to kill the sandboxee:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
s2.Kill()
|
||||||
|
```
|
||||||
|
|
||||||
|
Or just wait for completion and the final execution status:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto result = s2.AwaitResult();
|
||||||
|
LOG(INFO) << "Final execution status: " << result.ToString();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Test
|
||||||
|
|
||||||
|
Like regular code, your sandbox implementation should have tests. Sandbox tests
|
||||||
|
are not meant to test the program correctness, but instead to check whether the
|
||||||
|
sandboxed program can run without issues like sandbox violations. This also
|
||||||
|
makes sure that the policy is correct.
|
||||||
|
|
||||||
|
A sandboxed program is tested the same way it would run in production, with the
|
||||||
|
arguments and input files it would normally process.
|
||||||
|
|
||||||
|
It can be as simple as a shell test or C++ tests using sub processes. Check out
|
||||||
|
[the examples](examples.md) for inspiration.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Thanks for reading this far, we hope you liked our guide and now feel empowered
|
||||||
|
to create your own sandboxes to help keep your users safe.
|
||||||
|
|
||||||
|
Creating sandboxes and policies is a difficult task prone to subtle errors. To
|
||||||
|
remain on the safe side, have a security expert review your policy and code.
|
57
sandboxed_api/sandbox2/docs/howitworks.md
Normal file
57
sandboxed_api/sandbox2/docs/howitworks.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# How it works
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The sandbox technology is organized around 2 processes:
|
||||||
|
|
||||||
|
* An **executor** sets up and runs the *monitor*:
|
||||||
|
* Also known as *parent*, *supervisor* or *monitor*
|
||||||
|
* By itself is not sandboxed
|
||||||
|
* Is regular C++ code using the Sandbox2 API
|
||||||
|
|
||||||
|
* The **sandboxee**, a child program running in the sandboxed environment:
|
||||||
|
* Also known as *child* or *sandboxed process*
|
||||||
|
* Receives its policy from the executor and applies it
|
||||||
|
* Can come in different shapes:
|
||||||
|
* Another binary, like in the [crc4](../examples/crc4/crc4sandbox.cc) and
|
||||||
|
[static](../examples/static/static_sandbox.cc) examples
|
||||||
|
* A third party binary for which you do not have the source
|
||||||
|
|
||||||
|
Purpose/goal:
|
||||||
|
|
||||||
|
* Restrict the sandboxee to a set of allowed syscalls and their arguments
|
||||||
|
* The tighter the policy, the better
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
A really tight policy could deny all except reads and writes on standard
|
||||||
|
input and output file descriptors. Inside this sandbox, a program could take
|
||||||
|
input, process it, and send the output back.
|
||||||
|
* The processing is not allowed to make any other syscall, or else it is killed
|
||||||
|
for policy violation.
|
||||||
|
* If the processing is compromised (code execution by a malicious user), it
|
||||||
|
cannot do anything bad other than producing bad output (that the executor and
|
||||||
|
others still need to handle correctly).
|
||||||
|
|
||||||
|
|
||||||
|
## Sandbox Policies
|
||||||
|
|
||||||
|
The sandbox relies on **seccomp-bpf** provided by the Linux kernel. **seccomp**
|
||||||
|
is a Linux kernel facility for sandboxing and **BPF** is a way to write syscall
|
||||||
|
filters (the very same used for network filters). Read more about
|
||||||
|
[seccomp-bpf on Wikipedia](https://en.wikipedia.org/wiki/Seccomp#seccomp-bpf).
|
||||||
|
|
||||||
|
In practice, you will generate your policy using our
|
||||||
|
[PolicyBuilder class](../policybuilder.h). If you need more complex rules, you
|
||||||
|
can specify raw BPF macros, like in the [crc4](../examples/crc4/crc4sandbox.cc)
|
||||||
|
example.
|
||||||
|
|
||||||
|
Filesystem accesses are restricted with the help of Linux
|
||||||
|
[user namespaces][(http://man7.org/linux/man-pages/man7/user_namespaces.7.html).
|
||||||
|
User namespaces allow to drop the sandboxee into a custom chroot environment
|
||||||
|
without requiring root privileges.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Read our [Getting started](getting-started.md) page to set up your first
|
||||||
|
sandbox.
|
65
sandboxed_api/sandbox2/examples/crc4/BUILD.bazel
Normal file
65
sandboxed_api/sandbox2/examples/crc4/BUILD.bazel
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# The 'crc4' example demonstrates:
|
||||||
|
# - Separate executor and sandboxee
|
||||||
|
# - Sandboxee enables sandboxing by calling SandboxMeHere()
|
||||||
|
# - Strict syscall policy
|
||||||
|
# - Using sandbox2::Comms for data exchange (IPC)
|
||||||
|
# - Test to ensure sandbox executor runs sandboxee without issue
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
# Executor
|
||||||
|
cc_binary(
|
||||||
|
name = "crc4sandbox",
|
||||||
|
srcs = ["crc4sandbox.cc"],
|
||||||
|
data = [":crc4bin"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2",
|
||||||
|
"//sandboxed_api/sandbox2:comms",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/sandbox2/util:runfiles",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sandboxee
|
||||||
|
cc_binary(
|
||||||
|
name = "crc4bin",
|
||||||
|
srcs = ["crc4bin.cc"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2:client",
|
||||||
|
"//sandboxed_api/sandbox2:comms",
|
||||||
|
"//sandboxed_api/sandbox2:util",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test
|
||||||
|
cc_test(
|
||||||
|
name = "crc4sandbox_test",
|
||||||
|
srcs = ["crc4sandbox_test.cc"],
|
||||||
|
data = [":crc4sandbox"],
|
||||||
|
tags = ["local"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2:testing",
|
||||||
|
"//sandboxed_api/sandbox2:util",
|
||||||
|
"//sandboxed_api/util:status_matchers",
|
||||||
|
"@com_google_glog//:glog",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
77
sandboxed_api/sandbox2/examples/crc4/crc4bin.cc
Normal file
77
sandboxed_api/sandbox2/examples/crc4/crc4bin.cc
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// This file is an example of a computational-centric binary which is intended
|
||||||
|
// to be sandboxed by the sandbox2.
|
||||||
|
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "sandboxed_api/sandbox2/client.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util.h"
|
||||||
|
|
||||||
|
ABSL_FLAG(bool, call_syscall_not_allowed, false,
|
||||||
|
"Call a syscall that is not allowed by policy.");
|
||||||
|
|
||||||
|
// This function is insecure (i.e. can be crashed and exploited) to demonstrate
|
||||||
|
// how sandboxing can be helpful in defending against bugs.
|
||||||
|
// We need to make sure that this function is not inlined, so that we don't
|
||||||
|
// optimize the bug away.
|
||||||
|
static uint32_t ComputeCRC4Impl(const uint8_t* ptr, uint64_t len) {
|
||||||
|
uint8_t buf[8] = {0};
|
||||||
|
|
||||||
|
memcpy(buf, ptr, len); // buffer overflow!
|
||||||
|
|
||||||
|
uint32_t crc4 = 0;
|
||||||
|
for (uint64_t i = 0; i < len; i++) {
|
||||||
|
crc4 ^= (buf[i] << (i % 4) * 8);
|
||||||
|
}
|
||||||
|
return crc4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, false);
|
||||||
|
|
||||||
|
// Set-up the sandbox2::Client object, using a file descriptor (1023).
|
||||||
|
sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD);
|
||||||
|
sandbox2::Client sandbox2_client(&comms);
|
||||||
|
// Enable sandboxing from here.
|
||||||
|
sandbox2_client.SandboxMeHere();
|
||||||
|
|
||||||
|
// A syscall not allowed in policy, should cause a violation.
|
||||||
|
if (absl::GetFlag(FLAGS_call_syscall_not_allowed)) {
|
||||||
|
sandbox2::util::Syscall(__NR_sendfile, 0, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive data to be processed, process it, and return results.
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
if (!comms.RecvBytes(&buffer)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we don't inline the function. See the comment in
|
||||||
|
// ComputeCRC4Impl() for more details.
|
||||||
|
std::function<uint32_t(const uint8_t*, uint64_t)> ComputeCRC4 = ComputeCRC4Impl;
|
||||||
|
|
||||||
|
uint32_t crc4 = ComputeCRC4(buffer.data(), buffer.size());
|
||||||
|
|
||||||
|
if (!comms.SendUint32(crc4)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
154
sandboxed_api/sandbox2/examples/crc4/crc4sandbox.cc
Normal file
154
sandboxed_api/sandbox2/examples/crc4/crc4sandbox.cc
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// A demo sandbox for the crc4bin binary
|
||||||
|
|
||||||
|
#include <linux/filter.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/limits.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/result.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/runfiles.h"
|
||||||
|
|
||||||
|
ABSL_FLAG(string, input, "", "Input to calculate CRC4 of.");
|
||||||
|
ABSL_FLAG(bool, call_syscall_not_allowed, false,
|
||||||
|
"Have sandboxee call clone (violation).");
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::unique_ptr<sandbox2::Policy> GetPolicy() {
|
||||||
|
return sandbox2::PolicyBuilder()
|
||||||
|
.AllowExit()
|
||||||
|
.AddPolicyOnSyscalls(
|
||||||
|
{__NR_read, __NR_write, __NR_close},
|
||||||
|
{ARG_32(0), JEQ32(sandbox2::Comms::kSandbox2ClientCommsFD, ALLOW)})
|
||||||
|
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
|
||||||
|
defined(THREAD_SANITIZER)
|
||||||
|
.AllowSyscall(__NR_mmap)
|
||||||
|
#endif
|
||||||
|
.BuildOrDie();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SandboxedCRC4(sandbox2::Comms* comms, uint32_t* crc4) {
|
||||||
|
std::string input(absl::GetFlag(FLAGS_input));
|
||||||
|
|
||||||
|
const uint8_t* buf = reinterpret_cast<const uint8_t*>(input.data());
|
||||||
|
size_t buf_size = input.size();
|
||||||
|
|
||||||
|
if (!comms->SendBytes(buf, buf_size)) {
|
||||||
|
LOG(ERROR) << "sandboxee_comms->SendBytes() failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!comms->RecvUint32(crc4)) {
|
||||||
|
LOG(ERROR) << "sandboxee_comms->RecvUint32(&crc4) failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
if (absl::GetFlag(FLAGS_input).empty()) {
|
||||||
|
LOG(ERROR) << "Parameter --input required.";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path = sandbox2::GetInternalDataDependencyFilePath(
|
||||||
|
"sandbox2/examples/crc4/crc4bin");
|
||||||
|
std::vector<std::string> args = {path};
|
||||||
|
if (absl::GetFlag(FLAGS_call_syscall_not_allowed)) {
|
||||||
|
args.push_back("-call_syscall_not_allowed");
|
||||||
|
}
|
||||||
|
std::vector<std::string> envs = {};
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(path, args, envs);
|
||||||
|
|
||||||
|
executor
|
||||||
|
// Sandboxing is enabled by the binary itself (i.e. the crc4bin is capable
|
||||||
|
// of enabling sandboxing on its own).
|
||||||
|
->set_enable_sandbox_before_exec(false)
|
||||||
|
.limits()
|
||||||
|
// Remove restrictions on the size of address-space of sandboxed
|
||||||
|
// processes.
|
||||||
|
->set_rlimit_as(RLIM64_INFINITY)
|
||||||
|
// Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
|
||||||
|
// these many bytes to the file-system.
|
||||||
|
.set_rlimit_fsize(1024)
|
||||||
|
.set_rlimit_cpu(60) // The CPU time limit in seconds.
|
||||||
|
.set_walltime_limit(absl::Seconds(5));
|
||||||
|
|
||||||
|
auto* comms = executor->ipc()->comms();
|
||||||
|
auto policy = GetPolicy();
|
||||||
|
|
||||||
|
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
|
||||||
|
// Let the sandboxee run.
|
||||||
|
if (!s2.RunAsync()) {
|
||||||
|
auto result = s2.AwaitResult();
|
||||||
|
LOG(ERROR) << "RunAsync failed: " << result.ToString();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t crc4;
|
||||||
|
if (!SandboxedCRC4(comms, &crc4)) {
|
||||||
|
LOG(ERROR) << "GetCRC4 failed";
|
||||||
|
if (!s2.IsTerminated()) {
|
||||||
|
// Kill the sandboxee, because failure to receive the data over the Comms
|
||||||
|
// channel doesn't automatically mean that the sandboxee itself had
|
||||||
|
// already finished. The final reason will not be overwritten, so if
|
||||||
|
// sandboxee finished because of e.g. timeout, the TIMEOUT reason will be
|
||||||
|
// reported.
|
||||||
|
LOG(INFO) << "Killing sandboxee";
|
||||||
|
s2.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = s2.AwaitResult();
|
||||||
|
if (result.final_status() != sandbox2::Result::OK) {
|
||||||
|
LOG(ERROR) << "Sandbox error: " << result.ToString();
|
||||||
|
return 3; // e.g. sandbox violation, signal (sigsegv)
|
||||||
|
}
|
||||||
|
auto code = result.reason_code();
|
||||||
|
if (code) {
|
||||||
|
LOG(ERROR) << "Sandboxee exited with non-zero: " << code;
|
||||||
|
return 4; // e.g. normal child error
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Sandboxee finished: " << result.ToString();
|
||||||
|
printf("0x%08x\n", crc4);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
90
sandboxed_api/sandbox2/examples/crc4/crc4sandbox_test.cc
Normal file
90
sandboxed_api/sandbox2/examples/crc4/crc4sandbox_test.cc
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Unit tests for crc4sandbox example.
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "sandboxed_api/sandbox2/testing.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util.h"
|
||||||
|
#include "sandboxed_api/util/status_matchers.h"
|
||||||
|
|
||||||
|
using ::testing::Eq;
|
||||||
|
using ::testing::StrEq;
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class CRC4Test : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
path_ = GetTestSourcePath("sandbox2/examples/crc4/crc4sandbox");
|
||||||
|
util::CharPtrArrToVecString(environ, &env_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path_;
|
||||||
|
std::vector<std::string> env_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test that crc4sandbox works.
|
||||||
|
TEST_F(CRC4Test, TestNormalOperation) {
|
||||||
|
SKIP_SANITIZERS_AND_COVERAGE;
|
||||||
|
std::string output;
|
||||||
|
SAPI_ASSERT_OK_AND_ASSIGN(
|
||||||
|
int exit_code,
|
||||||
|
util::Communicate({path_, "-input", "ABCD"}, env_, &output));
|
||||||
|
|
||||||
|
EXPECT_THAT(output, StrEq("0x44434241\n"));
|
||||||
|
EXPECT_THAT(exit_code, Eq(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that crc4sandbox protects against bugs, because only the sandboxee
|
||||||
|
// will crash and break its communication with executor.
|
||||||
|
TEST_F(CRC4Test, TestExploitAttempt) {
|
||||||
|
SKIP_SANITIZERS_AND_COVERAGE;
|
||||||
|
|
||||||
|
std::string output;
|
||||||
|
SAPI_ASSERT_OK_AND_ASSIGN(
|
||||||
|
int exit_code,
|
||||||
|
util::Communicate(
|
||||||
|
{path_, "-input",
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},
|
||||||
|
env_, &output));
|
||||||
|
|
||||||
|
LOG(INFO) << "Output: " << output;
|
||||||
|
EXPECT_THAT(exit_code, Eq(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that if sandboxee calls a syscall that is not allowed by the policy,
|
||||||
|
// it triggers a policy violation for the executor.
|
||||||
|
TEST_F(CRC4Test, TestSyscallViolation) {
|
||||||
|
SKIP_SANITIZERS_AND_COVERAGE;
|
||||||
|
|
||||||
|
std::string output;
|
||||||
|
SAPI_ASSERT_OK_AND_ASSIGN(
|
||||||
|
int exit_code,
|
||||||
|
util::Communicate({path_, "-input", "x", "-call_syscall_not_allowed"},
|
||||||
|
env_, &output));
|
||||||
|
|
||||||
|
LOG(INFO) << "Output: " << output;
|
||||||
|
EXPECT_THAT(exit_code, Eq(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace sandbox2
|
48
sandboxed_api/sandbox2/examples/custom_fork/BUILD.bazel
Normal file
48
sandboxed_api/sandbox2/examples/custom_fork/BUILD.bazel
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# The 'custom_fork' example demonstrates how to:
|
||||||
|
# - create a custom fork-server, which will prepare and fork a sandboxee
|
||||||
|
# from the current process
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
# Executor
|
||||||
|
cc_binary(
|
||||||
|
name = "custom_fork_sandbox",
|
||||||
|
srcs = ["custom_fork_sandbox.cc"],
|
||||||
|
data = [":custom_fork_bin"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2",
|
||||||
|
"//sandboxed_api/sandbox2:comms",
|
||||||
|
"//sandboxed_api/sandbox2:forkserver",
|
||||||
|
"//sandboxed_api/sandbox2/util:runfiles",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
"@com_google_absl//absl/base:core_headers",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sandboxee
|
||||||
|
cc_binary(
|
||||||
|
name = "custom_fork_bin",
|
||||||
|
srcs = ["custom_fork_bin.cc"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2:comms",
|
||||||
|
"//sandboxed_api/sandbox2:forkingclient",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"//sandboxed_api/util:raw_logging",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// This file is an example of a binary which is intended to be sandboxed by the
|
||||||
|
// sandbox2, and which uses a built-in fork-server to spawn new sandboxees
|
||||||
|
// (instead of doing fork/execve via the Fork-Server).
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkingclient.h"
|
||||||
|
#include "sandboxed_api/util/raw_logging.h"
|
||||||
|
|
||||||
|
// Just return the value received over the Comms channel from the parent.
|
||||||
|
static int SandboxeeFunction(sandbox2::Comms* comms) {
|
||||||
|
int32_t i;
|
||||||
|
// SAPI_RAW_CHECK() uses smaller set of syscalls than regular CHECK().
|
||||||
|
SAPI_RAW_CHECK(comms->RecvInt32(&i), "Receiving an int32_t");
|
||||||
|
|
||||||
|
// Make sure that we're not the init process in the custom forkserver
|
||||||
|
// child.
|
||||||
|
SAPI_RAW_CHECK(getpid() == 2, "Unexpected PID");
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
// Writing to stderr limits the number of invoked syscalls.
|
||||||
|
google::SetCommandLineOptionWithMode("logtostderr", "true",
|
||||||
|
google::SET_FLAG_IF_DEFAULT);
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, false);
|
||||||
|
|
||||||
|
// Instantiate Comms channel with the parent Executor
|
||||||
|
sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD);
|
||||||
|
sandbox2::ForkingClient s2client(&comms);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
// Start a new process, if the sandboxer requests us to do so. No need to
|
||||||
|
// wait for the new process, as the call to sandbox2::Client::Fork will
|
||||||
|
// indirectly call sigaction(SIGCHLD, sa_flags=SA_NOCLDWAIT) in the parent.
|
||||||
|
pid_t pid = s2client.WaitAndFork();
|
||||||
|
if (pid == -1) {
|
||||||
|
SAPI_RAW_CHECK(false, "Could not spawn a new sandboxee");
|
||||||
|
}
|
||||||
|
// Child - return to the main(), to continue with code which is supposed to
|
||||||
|
// be sandboxed. From now on the comms channel (in the child) is set up over
|
||||||
|
// a new file descriptor pair, reachable from a separate Executor in the
|
||||||
|
// sandboxer.
|
||||||
|
if (pid == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start sandboxing here
|
||||||
|
s2client.SandboxMeHere();
|
||||||
|
|
||||||
|
// This section of code runs sandboxed
|
||||||
|
return SandboxeeFunction(&comms);
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// A demo sandbox for the custom_fork_bin binary.
|
||||||
|
// Use: custom_fork_sandbox --logtostderr
|
||||||
|
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/limits.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/result.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/runfiles.h"
|
||||||
|
|
||||||
|
std::unique_ptr<sandbox2::Policy> GetPolicy() {
|
||||||
|
return sandbox2::PolicyBuilder()
|
||||||
|
// The most frequent syscall should go first in this sequence (to make it
|
||||||
|
// fast).
|
||||||
|
.AllowRead()
|
||||||
|
.AllowWrite()
|
||||||
|
.AllowExit()
|
||||||
|
.AllowTime()
|
||||||
|
.AllowSyscalls({
|
||||||
|
__NR_close, __NR_getpid,
|
||||||
|
#if defined(__NR_arch_prctl)
|
||||||
|
// Not defined with every CPU architecture in Prod.
|
||||||
|
__NR_arch_prctl,
|
||||||
|
#endif // defined(__NR_arch_prctl)
|
||||||
|
})
|
||||||
|
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
|
||||||
|
defined(THREAD_SANITIZER)
|
||||||
|
.AllowMmap()
|
||||||
|
#endif
|
||||||
|
.EnableNamespaces()
|
||||||
|
.BuildOrDie();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int SandboxIteration(sandbox2::ForkClient* fork_client, int32_t i) {
|
||||||
|
// Now, start the sandboxee as usual, just use a different Executor
|
||||||
|
// constructor, which takes pointer to the ForkClient.
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(fork_client);
|
||||||
|
|
||||||
|
// Set limits as usual.
|
||||||
|
executor
|
||||||
|
->limits()
|
||||||
|
// Remove restrictions on the size of address-space of sandboxed
|
||||||
|
// processes. Here, it's 1GiB.
|
||||||
|
->set_rlimit_as(
|
||||||
|
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
|
||||||
|
defined(THREAD_SANITIZER)
|
||||||
|
RLIM64_INFINITY
|
||||||
|
#else
|
||||||
|
1ULL << 30 // 1GiB
|
||||||
|
#endif
|
||||||
|
)
|
||||||
|
// Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
|
||||||
|
// these many bytes to the file-system (including logs in prod, which
|
||||||
|
// write to files STDOUT and STDERR).
|
||||||
|
.set_rlimit_fsize(1024 /* bytes */)
|
||||||
|
// The CPU time limit.
|
||||||
|
.set_rlimit_cpu(10 /* CPU-seconds */)
|
||||||
|
.set_walltime_limit(absl::Seconds(5));
|
||||||
|
|
||||||
|
auto* comms = executor->ipc()->comms();
|
||||||
|
|
||||||
|
auto policy = GetPolicy();
|
||||||
|
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
|
||||||
|
// Let the sandboxee run (asynchronously).
|
||||||
|
CHECK(s2.RunAsync());
|
||||||
|
// Send integer, which will be returned as the sandboxee's exit code.
|
||||||
|
CHECK(comms->SendInt32(i));
|
||||||
|
auto result = s2.AwaitResult();
|
||||||
|
|
||||||
|
LOG(INFO) << "Final execution status of PID " << s2.GetPid() << ": "
|
||||||
|
<< result.ToString();
|
||||||
|
|
||||||
|
if (result.final_status() != sandbox2::Result::OK) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return result.reason_code();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
// Start a custom fork-server (via sandbox2::Executor).
|
||||||
|
const std::string path = sandbox2::GetInternalDataDependencyFilePath(
|
||||||
|
"sandbox2/examples/custom_fork/custom_fork_bin");
|
||||||
|
std::vector<std::string> args = {path};
|
||||||
|
std::vector<std::string> envs = {};
|
||||||
|
auto fork_executor = absl::make_unique<sandbox2::Executor>(path, args, envs);
|
||||||
|
// Start the fork-server (which is here: the custom_fork_bin process calling
|
||||||
|
// sandbox2::Client::WaitAndFork() in a loop).
|
||||||
|
//
|
||||||
|
// This function returns immediately, returning std::unique_ptr<ForkClient>.
|
||||||
|
//
|
||||||
|
// If it's holding the nullptr, then this call had failed.
|
||||||
|
auto fork_client = fork_executor->StartForkServer();
|
||||||
|
if (!fork_client) {
|
||||||
|
LOG(ERROR) << "Starting custom ForkServer failed";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Custom Fork-Server started";
|
||||||
|
|
||||||
|
// Test new sandboxees: send them integers over Comms, and expect they will
|
||||||
|
// exit with these specific exit codes.
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
CHECK_EQ(SandboxIteration(fork_client.get(), i), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
48
sandboxed_api/sandbox2/examples/static/BUILD.bazel
Normal file
48
sandboxed_api/sandbox2/examples/static/BUILD.bazel
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# The 'static' example demonstrates:
|
||||||
|
# - separate executor and sandboxee
|
||||||
|
# - sandboxee already sandboxed, not using google3 and compiled statically
|
||||||
|
# - minimal syscall policy written with BPF macros
|
||||||
|
# - communication with file descriptors and MapFd
|
||||||
|
# - test to ensure sandbox executor runs sandboxee without issue
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
# Executor
|
||||||
|
cc_binary(
|
||||||
|
name = "static_sandbox",
|
||||||
|
srcs = ["static_sandbox.cc"],
|
||||||
|
data = [":static_bin"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/sandbox2/util:runfiles",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sandboxee
|
||||||
|
# security: disable=cc-static-no-pie
|
||||||
|
cc_binary(
|
||||||
|
name = "static_bin",
|
||||||
|
srcs = ["static_bin.cc"],
|
||||||
|
features = [
|
||||||
|
"-pie",
|
||||||
|
"fully_static_link", # link libc statically
|
||||||
|
],
|
||||||
|
linkstatic = 1,
|
||||||
|
)
|
57
sandboxed_api/sandbox2/examples/static/static_bin.cc
Normal file
57
sandboxed_api/sandbox2/examples/static/static_bin.cc
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// This file is an example of a binary which is intended to be sandboxed by the
|
||||||
|
// sandbox2. It's not google3-based, and compiled statically (see BUILD).
|
||||||
|
//
|
||||||
|
// It inverts all bytes coming from stdin and writes them to the stdout.
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
char buf[1024];
|
||||||
|
size_t total_bytes = 0U;
|
||||||
|
|
||||||
|
fprintf(stderr, "=============================\n");
|
||||||
|
fprintf(stderr, "Starting file capitalization\n");
|
||||||
|
fprintf(stderr, "=============================\n");
|
||||||
|
fflush(nullptr);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
ssize_t sz = read(STDIN_FILENO, buf, sizeof(buf));
|
||||||
|
if (sz < 0) {
|
||||||
|
perror("read");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sz == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < sz; i++) {
|
||||||
|
buf[i] = toupper(buf[i]);
|
||||||
|
}
|
||||||
|
write(STDOUT_FILENO, buf, sz);
|
||||||
|
total_bytes += sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "=============================\n");
|
||||||
|
fprintf(stderr, "Converted: %zu bytes\n", total_bytes);
|
||||||
|
fprintf(stderr, "=============================\n");
|
||||||
|
fflush(nullptr);
|
||||||
|
return 0;
|
||||||
|
}
|
140
sandboxed_api/sandbox2/examples/static/static_sandbox.cc
Normal file
140
sandboxed_api/sandbox2/examples/static/static_sandbox.cc
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// A demo sandbox for the static_bin binary.
|
||||||
|
// Use: static_sandbox --logtostderr
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/limits.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/result.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/runfiles.h"
|
||||||
|
|
||||||
|
std::unique_ptr<sandbox2::Policy> GetPolicy() {
|
||||||
|
return sandbox2::PolicyBuilder()
|
||||||
|
// The most frequent syscall should go first in this sequence (to make it
|
||||||
|
// fast).
|
||||||
|
// Allow read() with all arguments.
|
||||||
|
.AllowRead()
|
||||||
|
// Allow a preset of syscalls that are known to be used during startup
|
||||||
|
// of static binaries.
|
||||||
|
.AllowStaticStartup()
|
||||||
|
// Allow the getpid() syscall.
|
||||||
|
.AllowSyscall(__NR_getpid)
|
||||||
|
|
||||||
|
// On Debian, even static binaries check existence of /etc/ld.so.nohwcap.
|
||||||
|
.BlockSyscallWithErrno(__NR_access, ENOENT)
|
||||||
|
|
||||||
|
// Examples for AddPolicyOnSyscall:
|
||||||
|
.AddPolicyOnSyscall(__NR_write,
|
||||||
|
{
|
||||||
|
// Load the first argument of write() (= fd)
|
||||||
|
ARG_32(0),
|
||||||
|
// Allow write(fd=STDOUT)
|
||||||
|
JEQ32(1, ALLOW),
|
||||||
|
// Allow write(fd=STDERR)
|
||||||
|
JEQ32(2, ALLOW),
|
||||||
|
// Fall-through for every other case.
|
||||||
|
// The default action will be KILL if it is not
|
||||||
|
// explicitly ALLOWed by a following rule.
|
||||||
|
})
|
||||||
|
// write() calls with fd not in (1, 2) will continue evaluating the
|
||||||
|
// policy. This means that other rules might still allow them.
|
||||||
|
|
||||||
|
// Allow exit() only with an exit_code of 0.
|
||||||
|
// Explicitly jumping to KILL, thus the following rules can not
|
||||||
|
// override this rule.
|
||||||
|
.AddPolicyOnSyscall(
|
||||||
|
__NR_exit_group,
|
||||||
|
{// Load first argument (exit_code).
|
||||||
|
ARG_32(0),
|
||||||
|
// Deny every argument except 0.
|
||||||
|
JNE32(0, KILL),
|
||||||
|
// Allow all exit() calls that were not previously forbidden
|
||||||
|
// = exit_code == 0.
|
||||||
|
ALLOW})
|
||||||
|
|
||||||
|
// = This won't have any effect as we handled every case of this syscall
|
||||||
|
// in the previous rule.
|
||||||
|
.AllowSyscall(__NR_exit_group)
|
||||||
|
|
||||||
|
#ifdef __NR_open
|
||||||
|
.BlockSyscallWithErrno(__NR_open, ENOENT)
|
||||||
|
#else
|
||||||
|
.BlockSyscallWithErrno(__NR_openat, ENOENT)
|
||||||
|
#endif
|
||||||
|
.EnableNamespaces()
|
||||||
|
.BuildOrDie();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
const std::string path = sandbox2::GetInternalDataDependencyFilePath(
|
||||||
|
"sandbox2/examples/static/static_bin");
|
||||||
|
std::vector<std::string> args = {path};
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(path, args);
|
||||||
|
|
||||||
|
executor
|
||||||
|
// Sandboxing is enabled by the sandbox itself. The sandboxed binary is
|
||||||
|
// not aware that it'll be sandboxed.
|
||||||
|
// Note: 'true' is the default setting for this class.
|
||||||
|
->set_enable_sandbox_before_exec(true)
|
||||||
|
.limits()
|
||||||
|
// Remove restrictions on the size of address-space of sandboxed
|
||||||
|
// processes.
|
||||||
|
->set_rlimit_as(RLIM64_INFINITY)
|
||||||
|
// Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
|
||||||
|
// these many bytes to the file-system.
|
||||||
|
.set_rlimit_fsize(1024)
|
||||||
|
// The CPU time limit.
|
||||||
|
.set_rlimit_cpu(60)
|
||||||
|
.set_walltime_limit(absl::Seconds(30));
|
||||||
|
|
||||||
|
int proc_version_fd = open("/proc/version", O_RDONLY);
|
||||||
|
PCHECK(proc_version_fd != -1);
|
||||||
|
|
||||||
|
// Map this fils to sandboxee's stdin.
|
||||||
|
executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
|
||||||
|
|
||||||
|
auto policy = GetPolicy();
|
||||||
|
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
|
||||||
|
// Let the sandboxee run (synchronously).
|
||||||
|
auto result = s2.Run();
|
||||||
|
|
||||||
|
LOG(INFO) << "Final execution status: " << result.ToString();
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
38
sandboxed_api/sandbox2/examples/tool/BUILD.bazel
Normal file
38
sandboxed_api/sandbox2/examples/tool/BUILD.bazel
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# The 'tool' example demonstrates:
|
||||||
|
# - a sandbox executor, sandboxee would be another program
|
||||||
|
# - sandboxee sandboxed before execve
|
||||||
|
# - very lax, separate sandbox policy written with BPFDSL
|
||||||
|
# - expose file descriptors to executor with ReceiveFd
|
||||||
|
# - set limits, wall time, filesystem checks, asynchronous run
|
||||||
|
# - test to ensure sandbox executor runs sandboxee without issue
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
# Executor
|
||||||
|
cc_binary(
|
||||||
|
name = "sandbox2tool",
|
||||||
|
srcs = ["sandbox2tool.cc"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2",
|
||||||
|
"//sandboxed_api/sandbox2:util",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/strings:str_format",
|
||||||
|
],
|
||||||
|
)
|
232
sandboxed_api/sandbox2/examples/tool/sandbox2tool.cc
Normal file
232
sandboxed_api/sandbox2/examples/tool/sandbox2tool.cc
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// A simple sandbox2 testing tool.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// sandbox2tool -v=1 -sandbox2_danger_danger_permit_all -logtostderr -- /bin/ls
|
||||||
|
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_split.h"
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/limits.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/result.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
|
||||||
|
|
||||||
|
ABSL_FLAG(bool, sandbox2tool_keep_env, false,
|
||||||
|
"Keep current environment variables");
|
||||||
|
ABSL_FLAG(bool, sandbox2tool_redirect_fd1, false,
|
||||||
|
"Receive sandboxee's STDOUT_FILENO (1) and output it locally");
|
||||||
|
ABSL_FLAG(bool, sandbox2tool_need_networking, false,
|
||||||
|
"If user namespaces are enabled, this option will enable "
|
||||||
|
"networking (by disabling the network namespace)");
|
||||||
|
ABSL_FLAG(bool, sandbox2tool_mount_tmp, false,
|
||||||
|
"If user namespaces are enabled, this option will create a tmpfs "
|
||||||
|
"mount at /tmp");
|
||||||
|
ABSL_FLAG(bool, sandbox2tool_resolve_and_add_libraries, false,
|
||||||
|
"resolve and mount the required libraries for the sandboxee");
|
||||||
|
ABSL_FLAG(bool, sandbox2tool_pause_resume, false,
|
||||||
|
"Pause the process after 3 seconds, resume after the subsequent "
|
||||||
|
"3 seconds, kill it after the final 3 seconds");
|
||||||
|
ABSL_FLAG(bool, sandbox2tool_pause_kill, false,
|
||||||
|
"Pause the process after 3 seconds, then SIGKILL it.");
|
||||||
|
ABSL_FLAG(bool, sandbox2tool_dump_stack, false,
|
||||||
|
"Dump the stack trace one second after the process is running.");
|
||||||
|
ABSL_FLAG(uint64_t, sandbox2tool_cpu_timeout, 60U,
|
||||||
|
"CPU timeout in seconds (if > 0)");
|
||||||
|
ABSL_FLAG(uint64_t, sandbox2tool_walltime_timeout, 60U,
|
||||||
|
"Wall-time timeout in seconds (if >0)");
|
||||||
|
ABSL_FLAG(uint64_t, sandbox2tool_file_size_creation_limit, 1024U,
|
||||||
|
"Maximum size of created files");
|
||||||
|
ABSL_FLAG(string, sandbox2tool_cwd, "/",
|
||||||
|
"If not empty, chdir to the directory before sandboxed");
|
||||||
|
ABSL_FLAG(string, sandbox2tool_additional_bind_mounts, "",
|
||||||
|
"If user namespaces are enabled, this option will add additional "
|
||||||
|
"bind mounts. Mounts are separated by comma and can optionally "
|
||||||
|
"specify a target using \"=>\" "
|
||||||
|
"(e.g. \"/usr,/bin,/lib,/tmp/foo=>/etc/passwd\")");
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void OutputFD(int fd) {
|
||||||
|
for (;;) {
|
||||||
|
char buf[4096];
|
||||||
|
ssize_t rlen = read(fd, buf, sizeof(buf));
|
||||||
|
if (rlen < 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Received from the sandboxee (FD STDOUT_FILENO (1)):"
|
||||||
|
<< "\n========================================\n"
|
||||||
|
<< std::string(buf, rlen)
|
||||||
|
<< "\n========================================\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
absl::FPrintF(stderr, "Usage: %s [flags] -- cmd args...", argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass everything after '--' to the sandbox.
|
||||||
|
std::vector<std::string> args;
|
||||||
|
sandbox2::util::CharPtrArrToVecString(&argv[1], &args);
|
||||||
|
|
||||||
|
// Pass the current environ pointer, depending on the flag.
|
||||||
|
std::vector<std::string> envp;
|
||||||
|
if (absl::GetFlag(FLAGS_sandbox2tool_keep_env)) {
|
||||||
|
sandbox2::util::CharPtrArrToVecString(environ, &envp);
|
||||||
|
}
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(argv[1], args, envp);
|
||||||
|
|
||||||
|
int recv_fd1 = -1;
|
||||||
|
if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
|
||||||
|
// Make the sandboxed process' fd be available as fd in the current process.
|
||||||
|
recv_fd1 = executor->ipc()->ReceiveFd(STDOUT_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
|
executor
|
||||||
|
->limits()
|
||||||
|
// Remove restrictions on the size of address-space of sandboxed
|
||||||
|
// processes.
|
||||||
|
->set_rlimit_as(RLIM64_INFINITY)
|
||||||
|
// Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
|
||||||
|
// this to the file-system.
|
||||||
|
.set_rlimit_fsize(
|
||||||
|
absl::GetFlag(FLAGS_sandbox2tool_file_size_creation_limit))
|
||||||
|
// An arbitrary, but empirically safe value.
|
||||||
|
.set_rlimit_nofile(1024U)
|
||||||
|
.set_walltime_limit(
|
||||||
|
absl::Seconds(absl::GetFlag(FLAGS_sandbox2tool_walltime_timeout)));
|
||||||
|
|
||||||
|
if (absl::GetFlag(FLAGS_sandbox2tool_cpu_timeout) > 0) {
|
||||||
|
executor->limits()->set_rlimit_cpu(
|
||||||
|
absl::GetFlag(FLAGS_sandbox2tool_cpu_timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
sandbox2::PolicyBuilder builder;
|
||||||
|
builder.AddPolicyOnSyscall(__NR_tee, {KILL});
|
||||||
|
builder.DangerDefaultAllowAll();
|
||||||
|
|
||||||
|
builder.EnableNamespaces();
|
||||||
|
|
||||||
|
if (absl::GetFlag(FLAGS_sandbox2tool_need_networking)) {
|
||||||
|
builder.AllowUnrestrictedNetworking();
|
||||||
|
}
|
||||||
|
if (absl::GetFlag(FLAGS_sandbox2tool_mount_tmp)) {
|
||||||
|
builder.AddTmpfs("/tmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mounts_string = absl::GetFlag(FLAGS_sandbox2tool_additional_bind_mounts);
|
||||||
|
if (!mounts_string.empty()) {
|
||||||
|
for (auto mount : absl::StrSplit(mounts_string, ',')) {
|
||||||
|
std::vector<std::string> source_target = absl::StrSplit(mount, "=>");
|
||||||
|
auto source = source_target[0];
|
||||||
|
auto target = source_target[0];
|
||||||
|
if (source_target.size() == 2) {
|
||||||
|
target = source_target[1];
|
||||||
|
}
|
||||||
|
struct stat64 st;
|
||||||
|
PCHECK(stat64(source.c_str(), &st) != -1)
|
||||||
|
<< "could not stat additional mount " << source;
|
||||||
|
if ((st.st_mode & S_IFMT) == S_IFDIR) {
|
||||||
|
builder.AddDirectoryAt(source, target, true);
|
||||||
|
} else {
|
||||||
|
builder.AddFileAt(source, target, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absl::GetFlag(FLAGS_sandbox2tool_resolve_and_add_libraries)) {
|
||||||
|
builder.AddLibrariesForBinary(argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto policy = builder.BuildOrDie();
|
||||||
|
|
||||||
|
// Current working directory.
|
||||||
|
if (!absl::GetFlag(FLAGS_sandbox2tool_cwd).empty()) {
|
||||||
|
executor->set_cwd(absl::GetFlag(FLAGS_sandbox2tool_cwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate the Sandbox2 object with policies and executors.
|
||||||
|
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
|
||||||
|
// This sandbox runs asynchronously. If there was no OutputFD() loop receiving
|
||||||
|
// the data from the recv_fd1, one could just use Sandbox2::Run().
|
||||||
|
if (s2.RunAsync()) {
|
||||||
|
if (absl::GetFlag(FLAGS_sandbox2tool_pause_resume)) {
|
||||||
|
sleep(3);
|
||||||
|
kill(s2.GetPid(), SIGSTOP);
|
||||||
|
sleep(3);
|
||||||
|
s2.SetWallTimeLimit(3);
|
||||||
|
kill(s2.GetPid(), SIGCONT);
|
||||||
|
} else if (absl::GetFlag(FLAGS_sandbox2tool_pause_kill)) {
|
||||||
|
sleep(3);
|
||||||
|
kill(s2.GetPid(), SIGSTOP);
|
||||||
|
sleep(1);
|
||||||
|
kill(s2.GetPid(), SIGKILL);
|
||||||
|
sleep(1);
|
||||||
|
} else if (absl::GetFlag(FLAGS_sandbox2tool_dump_stack)) {
|
||||||
|
sleep(1);
|
||||||
|
s2.DumpStackTrace();
|
||||||
|
} else if (absl::GetFlag(FLAGS_sandbox2tool_redirect_fd1)) {
|
||||||
|
OutputFD(recv_fd1);
|
||||||
|
// We couldn't receive more data from the sandboxee's STDOUT_FILENO, but
|
||||||
|
// the process could still be running. Kill it unconditionally. A correct
|
||||||
|
// final status code will be reported instead of Result::EXTERNAL_KILL.
|
||||||
|
s2.Kill();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Sandbox failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = s2.AwaitResult();
|
||||||
|
|
||||||
|
if (result.final_status() != sandbox2::Result::OK) {
|
||||||
|
LOG(ERROR) << "Sandbox error: " << result.ToString();
|
||||||
|
return 2; // sandbox violation
|
||||||
|
}
|
||||||
|
auto code = result.reason_code();
|
||||||
|
if (code) {
|
||||||
|
LOG(ERROR) << "Child exited with non-zero " << code;
|
||||||
|
return 1; // normal child error
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
41
sandboxed_api/sandbox2/examples/zlib/BUILD.bazel
Normal file
41
sandboxed_api/sandbox2/examples/zlib/BUILD.bazel
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
licenses(["notice"]) # Apache 2.0
|
||||||
|
|
||||||
|
# Executor
|
||||||
|
cc_binary(
|
||||||
|
name = "zpipe_sandbox",
|
||||||
|
srcs = ["zpipe_sandbox.cc"],
|
||||||
|
data = [":zpipe"],
|
||||||
|
deps = [
|
||||||
|
"//sandboxed_api/sandbox2",
|
||||||
|
"//sandboxed_api/sandbox2:comms",
|
||||||
|
"//sandboxed_api/sandbox2/util:bpf_helper",
|
||||||
|
"//sandboxed_api/sandbox2/util:runfiles",
|
||||||
|
"//sandboxed_api/util:flag",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sandboxee
|
||||||
|
cc_binary(
|
||||||
|
name = "zpipe",
|
||||||
|
srcs = ["zpipe.c"],
|
||||||
|
features = [
|
||||||
|
"fully_static_link", # link libc statically
|
||||||
|
],
|
||||||
|
linkstatic = 1,
|
||||||
|
deps = ["@net_zlib//:zlib"],
|
||||||
|
)
|
205
sandboxed_api/sandbox2/examples/zlib/zpipe.c
Normal file
205
sandboxed_api/sandbox2/examples/zlib/zpipe.c
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
/* zpipe.c: example of proper use of zlib's inflate() and deflate()
|
||||||
|
Not copyrighted -- provided to the public domain
|
||||||
|
Version 1.4 11 December 2005 Mark Adler */
|
||||||
|
|
||||||
|
/* Version history:
|
||||||
|
1.0 30 Oct 2004 First version
|
||||||
|
1.1 8 Nov 2004 Add void casting for unused return values
|
||||||
|
Use switch statement for inflate() return values
|
||||||
|
1.2 9 Nov 2004 Add assertions to document zlib guarantees
|
||||||
|
1.3 6 Apr 2005 Remove incorrect assertion in inf()
|
||||||
|
1.4 11 Dec 2005 Add hack to avoid MSDOS end-of-line conversions
|
||||||
|
Avoid some compiler warnings for input and output buffers
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "zlib.h"
|
||||||
|
|
||||||
|
#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
|
||||||
|
# include <fcntl.h>
|
||||||
|
# include <io.h>
|
||||||
|
# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
|
||||||
|
#else
|
||||||
|
# define SET_BINARY_MODE(file)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CHUNK 16384
|
||||||
|
|
||||||
|
/* Compress from file source to file dest until EOF on source.
|
||||||
|
def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
|
||||||
|
allocated for processing, Z_STREAM_ERROR if an invalid compression
|
||||||
|
level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
|
||||||
|
version of the library linked do not match, or Z_ERRNO if there is
|
||||||
|
an error reading or writing the files. */
|
||||||
|
int def(FILE *source, FILE *dest, int level)
|
||||||
|
{
|
||||||
|
int ret, flush;
|
||||||
|
unsigned have;
|
||||||
|
z_stream strm;
|
||||||
|
unsigned char in[CHUNK];
|
||||||
|
unsigned char out[CHUNK];
|
||||||
|
|
||||||
|
/* allocate deflate state */
|
||||||
|
strm.zalloc = Z_NULL;
|
||||||
|
strm.zfree = Z_NULL;
|
||||||
|
strm.opaque = Z_NULL;
|
||||||
|
ret = deflateInit(&strm, level);
|
||||||
|
if (ret != Z_OK)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* compress until end of file */
|
||||||
|
do {
|
||||||
|
strm.avail_in = fread(in, 1, CHUNK, source);
|
||||||
|
if (ferror(source)) {
|
||||||
|
(void)deflateEnd(&strm);
|
||||||
|
return Z_ERRNO;
|
||||||
|
}
|
||||||
|
flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
|
||||||
|
strm.next_in = in;
|
||||||
|
|
||||||
|
/* run deflate() on input until output buffer not full, finish
|
||||||
|
compression if all of source has been read in */
|
||||||
|
do {
|
||||||
|
strm.avail_out = CHUNK;
|
||||||
|
strm.next_out = out;
|
||||||
|
ret = deflate(&strm, flush); /* no bad return value */
|
||||||
|
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
|
||||||
|
have = CHUNK - strm.avail_out;
|
||||||
|
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
|
||||||
|
(void)deflateEnd(&strm);
|
||||||
|
return Z_ERRNO;
|
||||||
|
}
|
||||||
|
} while (strm.avail_out == 0);
|
||||||
|
assert(strm.avail_in == 0); /* all input will be used */
|
||||||
|
|
||||||
|
/* done when last data in file processed */
|
||||||
|
} while (flush != Z_FINISH);
|
||||||
|
assert(ret == Z_STREAM_END); /* stream will be complete */
|
||||||
|
|
||||||
|
/* clean up and return */
|
||||||
|
(void)deflateEnd(&strm);
|
||||||
|
return Z_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decompress from file source to file dest until stream ends or EOF.
|
||||||
|
inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be
|
||||||
|
allocated for processing, Z_DATA_ERROR if the deflate data is
|
||||||
|
invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and
|
||||||
|
the version of the library linked do not match, or Z_ERRNO if there
|
||||||
|
is an error reading or writing the files. */
|
||||||
|
int inf(FILE *source, FILE *dest)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
unsigned have;
|
||||||
|
z_stream strm;
|
||||||
|
unsigned char in[CHUNK];
|
||||||
|
unsigned char out[CHUNK];
|
||||||
|
|
||||||
|
/* allocate inflate state */
|
||||||
|
strm.zalloc = Z_NULL;
|
||||||
|
strm.zfree = Z_NULL;
|
||||||
|
strm.opaque = Z_NULL;
|
||||||
|
strm.avail_in = 0;
|
||||||
|
strm.next_in = Z_NULL;
|
||||||
|
ret = inflateInit(&strm);
|
||||||
|
if (ret != Z_OK)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* decompress until deflate stream ends or end of file */
|
||||||
|
do {
|
||||||
|
strm.avail_in = fread(in, 1, CHUNK, source);
|
||||||
|
if (ferror(source)) {
|
||||||
|
(void)inflateEnd(&strm);
|
||||||
|
return Z_ERRNO;
|
||||||
|
}
|
||||||
|
if (strm.avail_in == 0)
|
||||||
|
break;
|
||||||
|
strm.next_in = in;
|
||||||
|
|
||||||
|
/* run inflate() on input until output buffer not full */
|
||||||
|
do {
|
||||||
|
strm.avail_out = CHUNK;
|
||||||
|
strm.next_out = out;
|
||||||
|
ret = inflate(&strm, Z_NO_FLUSH);
|
||||||
|
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
|
||||||
|
switch (ret) {
|
||||||
|
case Z_NEED_DICT:
|
||||||
|
ret = Z_DATA_ERROR; /* and fall through */
|
||||||
|
case Z_DATA_ERROR:
|
||||||
|
case Z_MEM_ERROR:
|
||||||
|
(void)inflateEnd(&strm);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
have = CHUNK - strm.avail_out;
|
||||||
|
if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
|
||||||
|
(void)inflateEnd(&strm);
|
||||||
|
return Z_ERRNO;
|
||||||
|
}
|
||||||
|
} while (strm.avail_out == 0);
|
||||||
|
|
||||||
|
/* done when inflate() says it's done */
|
||||||
|
} while (ret != Z_STREAM_END);
|
||||||
|
|
||||||
|
/* clean up and return */
|
||||||
|
(void)inflateEnd(&strm);
|
||||||
|
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* report a zlib or i/o error */
|
||||||
|
void zerr(int ret)
|
||||||
|
{
|
||||||
|
fputs("zpipe: ", stderr);
|
||||||
|
switch (ret) {
|
||||||
|
case Z_ERRNO:
|
||||||
|
if (ferror(stdin))
|
||||||
|
fputs("error reading stdin\n", stderr);
|
||||||
|
if (ferror(stdout))
|
||||||
|
fputs("error writing stdout\n", stderr);
|
||||||
|
break;
|
||||||
|
case Z_STREAM_ERROR:
|
||||||
|
fputs("invalid compression level\n", stderr);
|
||||||
|
break;
|
||||||
|
case Z_DATA_ERROR:
|
||||||
|
fputs("invalid or incomplete deflate data\n", stderr);
|
||||||
|
break;
|
||||||
|
case Z_MEM_ERROR:
|
||||||
|
fputs("out of memory\n", stderr);
|
||||||
|
break;
|
||||||
|
case Z_VERSION_ERROR:
|
||||||
|
fputs("zlib version mismatch!\n", stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compress or decompress from stdin to stdout */
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* avoid end-of-line conversions */
|
||||||
|
SET_BINARY_MODE(stdin);
|
||||||
|
SET_BINARY_MODE(stdout);
|
||||||
|
|
||||||
|
/* do compression if no arguments */
|
||||||
|
if (argc == 1) {
|
||||||
|
ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
|
||||||
|
if (ret != Z_OK)
|
||||||
|
zerr(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do decompression if -d specified */
|
||||||
|
else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
|
||||||
|
ret = inf(stdin, stdout);
|
||||||
|
if (ret != Z_OK)
|
||||||
|
zerr(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* otherwise, report usage */
|
||||||
|
else {
|
||||||
|
fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
124
sandboxed_api/sandbox2/examples/zlib/zpipe_sandbox.cc
Normal file
124
sandboxed_api/sandbox2/examples/zlib/zpipe_sandbox.cc
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/filter.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/util/flag.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/limits.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policybuilder.h"
|
||||||
|
#include "sandboxed_api/sandbox2/result.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sandbox2.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/runfiles.h"
|
||||||
|
|
||||||
|
ABSL_FLAG(string, input, "", "Input file");
|
||||||
|
ABSL_FLAG(string, output, "", "Output file");
|
||||||
|
ABSL_FLAG(bool, decompress, false, "Decompress instead of compress.");
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::unique_ptr<sandbox2::Policy> GetPolicy() {
|
||||||
|
return sandbox2::PolicyBuilder()
|
||||||
|
// Allow read on STDIN.
|
||||||
|
.AddPolicyOnSyscall(__NR_read, {ARG_32(0), JEQ32(0, ALLOW)})
|
||||||
|
// Allow write on STDOUT / STDERR.
|
||||||
|
.AddPolicyOnSyscall(__NR_write,
|
||||||
|
{ARG_32(0), JEQ32(1, ALLOW), JEQ32(2, ALLOW)})
|
||||||
|
.AllowSyscall(__NR_fstat)
|
||||||
|
.AllowStaticStartup()
|
||||||
|
.AllowSystemMalloc()
|
||||||
|
.AllowExit()
|
||||||
|
.EnableNamespaces()
|
||||||
|
.BuildOrDie();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
if (absl::GetFlag(FLAGS_input).empty()) {
|
||||||
|
LOG(ERROR) << "Parameter --input required.";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absl::GetFlag(FLAGS_output).empty()) {
|
||||||
|
LOG(ERROR) << "Parameter --output required.";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path = sandbox2::GetInternalDataDependencyFilePath(
|
||||||
|
"sandbox2/examples/zlib/zpipe");
|
||||||
|
std::vector<std::string> args = {path};
|
||||||
|
if (absl::GetFlag(FLAGS_decompress)) {
|
||||||
|
args.push_back("-d");
|
||||||
|
}
|
||||||
|
std::vector<std::string> envs = {};
|
||||||
|
auto executor = absl::make_unique<sandbox2::Executor>(path, args, envs);
|
||||||
|
|
||||||
|
executor
|
||||||
|
// Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
|
||||||
|
// these many bytes to the file-system.
|
||||||
|
->limits()
|
||||||
|
->set_rlimit_fsize(1ULL << 30) // 1GiB
|
||||||
|
.set_rlimit_cpu(60) // The CPU time limit in seconds.
|
||||||
|
.set_walltime_limit(absl::Seconds(5));
|
||||||
|
|
||||||
|
// Create input + output FD.
|
||||||
|
int fd_in = open(absl::GetFlag(FLAGS_input).c_str(), O_RDONLY);
|
||||||
|
int fd_out = open(absl::GetFlag(FLAGS_output).c_str(),
|
||||||
|
O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||||
|
CHECK_GE(fd_in, 0);
|
||||||
|
CHECK_GE(fd_out, 0);
|
||||||
|
executor->ipc()->MapFd(fd_in, STDIN_FILENO);
|
||||||
|
executor->ipc()->MapFd(fd_out, STDOUT_FILENO);
|
||||||
|
|
||||||
|
auto policy = GetPolicy();
|
||||||
|
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
|
||||||
|
|
||||||
|
// Let the sandboxee run.
|
||||||
|
auto result = s2.Run();
|
||||||
|
close(fd_in);
|
||||||
|
close(fd_out);
|
||||||
|
if (result.final_status() != sandbox2::Result::OK) {
|
||||||
|
LOG(ERROR) << "Sandbox error: " << result.ToString();
|
||||||
|
return 2; // e.g. sandbox violation, signal (sigsegv)
|
||||||
|
}
|
||||||
|
auto code = result.reason_code();
|
||||||
|
if (code) {
|
||||||
|
LOG(ERROR) << "Sandboxee exited with non-zero: " << code;
|
||||||
|
return 3; // e.g. normal child error
|
||||||
|
}
|
||||||
|
LOG(INFO) << "Sandboxee finished: " << result.ToString();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
208
sandboxed_api/sandbox2/executor.cc
Normal file
208
sandboxed_api/sandbox2/executor.cc
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Implementation of the sandbox2::Executor class
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/executor.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include <sys/capability.h>
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.pb.h"
|
||||||
|
#include "sandboxed_api/sandbox2/global_forkclient.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/fileops.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
// Delegate constructor that gets called by the public ones.
|
||||||
|
Executor::Executor(int exec_fd, const std::string& path,
|
||||||
|
const std::vector<std::string>& argv,
|
||||||
|
const std::vector<std::string>& envp,
|
||||||
|
bool enable_sandboxing_pre_execve,
|
||||||
|
pid_t libunwind_sbox_for_pid, ForkClient* fork_client)
|
||||||
|
: libunwind_sbox_for_pid_(libunwind_sbox_for_pid),
|
||||||
|
enable_sandboxing_pre_execve_(enable_sandboxing_pre_execve),
|
||||||
|
exec_fd_(exec_fd),
|
||||||
|
path_(path),
|
||||||
|
argv_(argv),
|
||||||
|
envp_(envp) {
|
||||||
|
if (fork_client != nullptr) {
|
||||||
|
CHECK(exec_fd == -1 && path.empty());
|
||||||
|
fork_client_ = fork_client;
|
||||||
|
} else {
|
||||||
|
CHECK((exec_fd == -1 && (!path.empty() || libunwind_sbox_for_pid > 0)) ||
|
||||||
|
(exec_fd >= 0 && path.empty()));
|
||||||
|
fork_client_ = GetGlobalForkClient();
|
||||||
|
}
|
||||||
|
SetUpServerSideCommsFd();
|
||||||
|
SetDefaultCwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> Executor::CopyEnviron() {
|
||||||
|
std::vector<std::string> environ_copy;
|
||||||
|
util::CharPtrArrToVecString(environ, &environ_copy);
|
||||||
|
return environ_copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t Executor::StartSubProcess(int32_t clone_flags, const Namespace* ns,
|
||||||
|
const std::vector<cap_value_t>* caps,
|
||||||
|
pid_t* init_pid_out) {
|
||||||
|
if (started_) {
|
||||||
|
LOG(ERROR) << "This executor has already been started";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (fork_client_ == nullptr) {
|
||||||
|
LOG(ERROR) << "The ForkClient object is not instantiated";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path_.empty()) {
|
||||||
|
exec_fd_ = open(path_.c_str(), O_PATH);
|
||||||
|
if (exec_fd_ < 0) {
|
||||||
|
LOG(ERROR) << "Could not open file " << path_;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libunwind_sbox_for_pid_ != 0) {
|
||||||
|
VLOG(1) << "StartSubProcces, starting libunwind";
|
||||||
|
} else if (exec_fd_ < 0) {
|
||||||
|
VLOG(1) << "StartSubProcess, with [Fork-Server]";
|
||||||
|
} else if (!path_.empty()) {
|
||||||
|
VLOG(1) << "StartSubProcess, with file " << path_;
|
||||||
|
} else {
|
||||||
|
VLOG(1) << "StartSubProcess, with fd " << exec_fd_;
|
||||||
|
}
|
||||||
|
|
||||||
|
ForkRequest request;
|
||||||
|
for (size_t i = 0; i < argv_.size(); i++) {
|
||||||
|
request.add_args(argv_[i]);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < envp_.size(); i++) {
|
||||||
|
request.add_envs(envp_[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add LD_ORIGIN_PATH to envs, as it'll make the amount of syscalls invoked by
|
||||||
|
// ld.so smaller. See http://b/7626303 for more details on this behavior.
|
||||||
|
if (!path_.empty()) {
|
||||||
|
request.add_envs(absl::StrCat("LD_ORIGIN_PATH=",
|
||||||
|
file_util::fileops::StripBasename(path_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If neither the path, nor exec_fd is specified, just assume that we need to
|
||||||
|
// send a fork request.
|
||||||
|
//
|
||||||
|
// Otherwise, it's either sandboxing pre- or post-execve with the global
|
||||||
|
// Fork-Server.
|
||||||
|
if (libunwind_sbox_for_pid_ != 0) {
|
||||||
|
request.set_mode(FORKSERVER_FORK_JOIN_SANDBOX_UNWIND);
|
||||||
|
} else if (exec_fd_ == -1) {
|
||||||
|
request.set_mode(FORKSERVER_FORK);
|
||||||
|
} else if (enable_sandboxing_pre_execve_) {
|
||||||
|
request.set_mode(FORKSERVER_FORK_EXECVE_SANDBOX);
|
||||||
|
} else {
|
||||||
|
request.set_mode(FORKSERVER_FORK_EXECVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ns) {
|
||||||
|
clone_flags |= ns->GetCloneFlags();
|
||||||
|
*request.mutable_mount_tree() = ns->mounts().GetMountTree();
|
||||||
|
request.set_hostname(ns->GetHostname());
|
||||||
|
}
|
||||||
|
|
||||||
|
request.set_clone_flags(clone_flags);
|
||||||
|
|
||||||
|
if (caps) {
|
||||||
|
for (auto cap : *caps) {
|
||||||
|
request.add_capabilities(cap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ns_fd = -1;
|
||||||
|
if (libunwind_sbox_for_pid_ != 0) {
|
||||||
|
std::string ns_path =
|
||||||
|
absl::StrCat("/proc/", libunwind_sbox_for_pid_, "/ns/user");
|
||||||
|
PCHECK((ns_fd = open(ns_path.c_str(), O_RDONLY)) != -1)
|
||||||
|
<< "Could not open user ns fd (" << ns_path << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t init_pid = -1;
|
||||||
|
|
||||||
|
pid_t sandboxee_pid = fork_client_->SendRequest(
|
||||||
|
request, exec_fd_, client_comms_fd_, ns_fd, &init_pid);
|
||||||
|
|
||||||
|
// init_pid = 0 means that we're executing the libunwind sandbox and don't
|
||||||
|
// need an init process.
|
||||||
|
// TODO(hamacher): This is also the case for spawning the custom forksever
|
||||||
|
// (not spawning children from the custom forkserver), so
|
||||||
|
// we should clean it up.
|
||||||
|
if (init_pid == -1) {
|
||||||
|
LOG(ERROR) << "Could not obtain init PID";
|
||||||
|
} else if (init_pid > 0) {
|
||||||
|
if (init_pid_out) {
|
||||||
|
*init_pid_out = init_pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
started_ = true;
|
||||||
|
|
||||||
|
close(client_comms_fd_);
|
||||||
|
client_comms_fd_ = -1;
|
||||||
|
if (exec_fd_ >= 0) {
|
||||||
|
close(exec_fd_);
|
||||||
|
exec_fd_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ns_fd >= 0) {
|
||||||
|
close(ns_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
VLOG(1) << "StartSubProcess returned with: " << sandboxee_pid;
|
||||||
|
return sandboxee_pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ForkClient> Executor::StartForkServer() {
|
||||||
|
// This flag is set explicitly to 'true' during object instantiation, and
|
||||||
|
// custom fork-servers should never be sandboxed.
|
||||||
|
set_enable_sandbox_before_exec(false);
|
||||||
|
if (StartSubProcess(0) == -1) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return absl::make_unique<ForkClient>(ipc_.comms());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Executor::SetUpServerSideCommsFd() {
|
||||||
|
int sv[2];
|
||||||
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
|
||||||
|
PLOG(FATAL) << "socketpair(AF_UNIX, SOCK_STREAM) failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
client_comms_fd_ = sv[0];
|
||||||
|
server_comms_fd_ = sv[1];
|
||||||
|
|
||||||
|
ipc_.SetUpServerSideComms(server_comms_fd_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
170
sandboxed_api/sandbox2/executor.h
Normal file
170
sandboxed_api/sandbox2/executor.h
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX2_EXECUTOR_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX2_EXECUTOR_H_
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/capability.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "absl/base/macros.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/limits.h"
|
||||||
|
#include "sandboxed_api/sandbox2/namespace.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
// The sandbox2::Executor class is responsible for both creating and executing
|
||||||
|
// new processes which will be sandboxed.
|
||||||
|
class Executor final {
|
||||||
|
public:
|
||||||
|
Executor(const Executor&) = delete;
|
||||||
|
Executor& operator=(const Executor&) = delete;
|
||||||
|
|
||||||
|
// Initialized with a path to the process that the Executor class will
|
||||||
|
// execute
|
||||||
|
Executor(const std::string& path, const std::vector<std::string>& argv)
|
||||||
|
: Executor(/*exec_fd=*/-1, path, argv, CopyEnviron(),
|
||||||
|
/*enable_sandboxing_pre_execve=*/true,
|
||||||
|
/*libunwind_sbox_for_pid=*/0,
|
||||||
|
/*fork_client=*/nullptr) {}
|
||||||
|
|
||||||
|
// As above, but takes an explicit environment
|
||||||
|
Executor(const std::string& path, const std::vector<std::string>& argv,
|
||||||
|
const std::vector<std::string>& envp)
|
||||||
|
: Executor(/*exec_fd=*/-1, path, argv, envp,
|
||||||
|
/*enable_sandboxing_pre_execve=*/true,
|
||||||
|
/*libunwind_sbox_for_pid=*/0,
|
||||||
|
/*fork_client=*/nullptr) {}
|
||||||
|
|
||||||
|
// As above, but takes a file-descriptor referring to an executable file.
|
||||||
|
// Executor will own this file-descriptor, so if intend to use it, pass here
|
||||||
|
// dup(fd) instead
|
||||||
|
Executor(int exec_fd, const std::vector<std::string>& argv,
|
||||||
|
const std::vector<std::string>& envp)
|
||||||
|
: Executor(exec_fd, /*path=*/"", argv, envp,
|
||||||
|
/*enable_sandboxing_pre_execve=*/true,
|
||||||
|
/*libunwind_sbox_for_pid=*/0,
|
||||||
|
/*fork_client=*/nullptr) {}
|
||||||
|
|
||||||
|
// Uses a custom ForkServer (which the supplied ForkClient can communicate
|
||||||
|
// with), which knows how to fork (or even execute) new sandboxed processes
|
||||||
|
// (hence, no need to supply path/argv/envp here)
|
||||||
|
explicit Executor(ForkClient* fork_client)
|
||||||
|
: Executor(/*exec_fd=*/-1, /*path=*/"", /*argv=*/{}, /*envp=*/{},
|
||||||
|
/*enable_sandboxing_pre_execve=*/false,
|
||||||
|
/*libunwind_sbox_for_pid=*/0, fork_client) {}
|
||||||
|
|
||||||
|
// Creates a new process which will act as a custom ForkServer. Should be used
|
||||||
|
// with custom fork servers only.
|
||||||
|
// This function returns immediately and returns a nullptr on failure.
|
||||||
|
std::unique_ptr<ForkClient> StartForkServer();
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
IPC* ipc() { return &ipc_; }
|
||||||
|
Limits* limits() { return &limits_; }
|
||||||
|
Executor& set_enable_sandbox_before_exec(bool value) {
|
||||||
|
enable_sandboxing_pre_execve_ = value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Executor& set_cwd(std::string value) {
|
||||||
|
cwd_ = std::move(value);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Monitor;
|
||||||
|
friend class StackTracePeer;
|
||||||
|
|
||||||
|
// Internal constructor for executing libunwind on the given pid
|
||||||
|
// enable_sandboxing_pre_execve=false as we are not going to execve.
|
||||||
|
explicit Executor(pid_t libunwind_sbox_for_pid)
|
||||||
|
: Executor(/*exec_fd=*/-1, /*path=*/"", /*argv=*/{}, /*envp=*/{},
|
||||||
|
/*enable_sandboxing_pre_execve=*/false,
|
||||||
|
/*libunwind_sbox_for_pid=*/libunwind_sbox_for_pid,
|
||||||
|
/*fork_client=*/nullptr) {}
|
||||||
|
|
||||||
|
// Delegate constructor that gets called by the public ones.
|
||||||
|
Executor(int exec_fd, const std::string& path, const std::vector<std::string>& argv,
|
||||||
|
const std::vector<std::string>& envp, bool enable_sandboxing_pre_execve,
|
||||||
|
pid_t libunwind_sbox_for_pid, ForkClient* fork_client);
|
||||||
|
|
||||||
|
// Creates a copy of the environment
|
||||||
|
static std::vector<std::string> CopyEnviron();
|
||||||
|
|
||||||
|
// Creates a server-side Comms end-point using a pre-connected file
|
||||||
|
// descriptor.
|
||||||
|
void SetUpServerSideCommsFd();
|
||||||
|
|
||||||
|
// Sets the default value for cwd_
|
||||||
|
void SetDefaultCwd() {
|
||||||
|
char* cwd = get_current_dir_name();
|
||||||
|
PCHECK(cwd != nullptr);
|
||||||
|
cwd_ = cwd;
|
||||||
|
free(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts a new process which is connected with this Executor instance via a
|
||||||
|
// Comms channel.
|
||||||
|
// For clone_flags refer to Linux' 'man 2 clone'.
|
||||||
|
//
|
||||||
|
// caps is a vector of capabilities that are kept in the permitted set after
|
||||||
|
// the clone, use with caution.
|
||||||
|
//
|
||||||
|
// Returns the same values as fork().
|
||||||
|
pid_t StartSubProcess(int clone_flags, const Namespace* ns = nullptr,
|
||||||
|
const std::vector<cap_value_t>* caps = nullptr,
|
||||||
|
pid_t* init_pid_out = nullptr);
|
||||||
|
|
||||||
|
// Whether the Executor has been started yet
|
||||||
|
bool started_ = false;
|
||||||
|
|
||||||
|
// If this executor is running the libunwind sandbox for a process,
|
||||||
|
// this variable will hold the PID of the process. Otherwise it is zero.
|
||||||
|
pid_t libunwind_sbox_for_pid_;
|
||||||
|
|
||||||
|
// Should the sandboxing be enabled before execve() occurs, or the binary will
|
||||||
|
// do it by itself, using the Client object's methods
|
||||||
|
bool enable_sandboxing_pre_execve_;
|
||||||
|
|
||||||
|
// Alternate (path/fd)/argv/envp to be used the in the __NR_execve call.
|
||||||
|
int exec_fd_;
|
||||||
|
std::string path_;
|
||||||
|
std::vector<std::string> argv_;
|
||||||
|
std::vector<std::string> envp_;
|
||||||
|
|
||||||
|
// chdir to cwd_, if set.
|
||||||
|
std::string cwd_;
|
||||||
|
|
||||||
|
// Server (sandbox) end-point of a socket-pair used to create Comms channel
|
||||||
|
int server_comms_fd_ = -1;
|
||||||
|
// Client (sandboxee) end-point of a socket-pair used to create Comms channel
|
||||||
|
int client_comms_fd_ = -1;
|
||||||
|
|
||||||
|
// ForkClient connecting to the ForkServer - not owned by the object
|
||||||
|
ForkClient* fork_client_;
|
||||||
|
|
||||||
|
IPC ipc_; // Used for communication with the sandboxee
|
||||||
|
Limits limits_; // Defines server- and client-side limits
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX2_EXECUTOR_H_
|
41
sandboxed_api/sandbox2/forkingclient.cc
Normal file
41
sandboxed_api/sandbox2/forkingclient.cc
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/forkingclient.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "sandboxed_api/sandbox2/sanitizer.h"
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
pid_t ForkingClient::WaitAndFork() {
|
||||||
|
// We don't instantiate the Fork-Server until the first Fork() call takes
|
||||||
|
// place (in order to conserve resources, and avoid calling Fork-Server
|
||||||
|
// initialization routines).
|
||||||
|
if (!fork_server_worker_) {
|
||||||
|
sanitizer::WaitForTsan();
|
||||||
|
// Perform that check once only, because it's quite CPU-expensive.
|
||||||
|
int n = sanitizer::GetNumberOfThreads(getpid());
|
||||||
|
CHECK_NE(n, -1) << "sanitizer::GetNumberOfThreads failed";
|
||||||
|
CHECK_EQ(n, 1) << "Too many threads (" << n
|
||||||
|
<< ") during sandbox2::Client::WaitAndFork()";
|
||||||
|
fork_server_worker_ = absl::make_unique<ForkServer>(comms_);
|
||||||
|
}
|
||||||
|
return fork_server_worker_->ServeRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
47
sandboxed_api/sandbox2/forkingclient.h
Normal file
47
sandboxed_api/sandbox2/forkingclient.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX2_FORKINGCLIENT_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX2_FORKINGCLIENT_H_
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/client.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
class ForkingClient : public Client {
|
||||||
|
public:
|
||||||
|
explicit ForkingClient(Comms* comms) : Client(comms) {}
|
||||||
|
|
||||||
|
// Forks the current process (if asked by the Executor in the parent process),
|
||||||
|
// and returns the newly created PID to this Executor. This is used if the
|
||||||
|
// current Client objects acts as a wrapper of ForkServer (and this process
|
||||||
|
// was created to act as a ForkServer).
|
||||||
|
// Return values specified as with 'fork' (incl. -1).
|
||||||
|
pid_t WaitAndFork();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// ForkServer object, which is used only if the current process is meant
|
||||||
|
// to behave like a Fork-Server, i.e. to create a new process which will be
|
||||||
|
// later sandboxed (with SandboxMeHere()).
|
||||||
|
std::unique_ptr<ForkServer> fork_server_worker_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX2_FORKINGCLIENT_H_
|
537
sandboxed_api/sandbox2/forkserver.cc
Normal file
537
sandboxed_api/sandbox2/forkserver.cc
Normal file
|
@ -0,0 +1,537 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Implementation of the sandbox2::ForkServer class.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.h"
|
||||||
|
|
||||||
|
#include <asm/types.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <sys/capability.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "absl/strings/match.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_join.h"
|
||||||
|
#include "absl/synchronization/mutex.h"
|
||||||
|
#include "sandboxed_api/sandbox2/client.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.pb.h"
|
||||||
|
#include "sandboxed_api/sandbox2/namespace.h"
|
||||||
|
#include "sandboxed_api/sandbox2/policy.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sanitizer.h"
|
||||||
|
#include "sandboxed_api/sandbox2/syscall.h"
|
||||||
|
#include "sandboxed_api/sandbox2/unwind/ptrace_hook.h"
|
||||||
|
#include "sandboxed_api/sandbox2/unwind/unwind.h"
|
||||||
|
#include "sandboxed_api/sandbox2/unwind/unwind.pb.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/fileops.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/strerror.h"
|
||||||
|
#include "sandboxed_api/util/raw_logging.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
pid_t ForkClient::SendRequest(const ForkRequest& request, int exec_fd,
|
||||||
|
int comms_fd, int user_ns_fd, pid_t* init_pid) {
|
||||||
|
// Acquire the channel ownership for this request (transaction).
|
||||||
|
absl::MutexLock l(&comms_mutex_);
|
||||||
|
|
||||||
|
if (!comms_->SendProtoBuf(request)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Sending PB to the ForkServer failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!comms_->SendFD(comms_fd)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Sending Comms FD (%d) to the ForkServer failed",
|
||||||
|
comms_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (request.mode() == FORKSERVER_FORK_EXECVE ||
|
||||||
|
request.mode() == FORKSERVER_FORK_EXECVE_SANDBOX) {
|
||||||
|
if (!comms_->SendFD(exec_fd)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Sending Exec FD (%d) to the ForkServer failed",
|
||||||
|
exec_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.mode() == FORKSERVER_FORK_JOIN_SANDBOX_UNWIND) {
|
||||||
|
if (!comms_->SendFD(user_ns_fd)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Sending user ns FD (%d) to the ForkServer failed",
|
||||||
|
user_ns_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t pid;
|
||||||
|
// Receive init process ID.
|
||||||
|
if (!comms_->RecvInt32(&pid)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Receiving init PID from the ForkServer failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (init_pid) {
|
||||||
|
*init_pid = static_cast<pid_t>(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive sandboxee process ID.
|
||||||
|
if (!comms_->RecvInt32(&pid)) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Receiving sandboxee PID from the ForkServer failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return static_cast<pid_t>(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForkServer::PrepareExecveArgs(const ForkRequest& request,
|
||||||
|
std::vector<std::string>* args,
|
||||||
|
std::vector<std::string>* envp) {
|
||||||
|
// Prepare arguments for execve.
|
||||||
|
for (const auto& arg : request.args()) {
|
||||||
|
args->push_back(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare environment variables for execve.
|
||||||
|
for (const auto& env : request.envs()) {
|
||||||
|
envp->push_back(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The child process should not start any fork-servers.
|
||||||
|
envp->push_back(absl::StrCat(kForkServerDisableEnv, "=1"));
|
||||||
|
|
||||||
|
constexpr char kSapiVlogLevel[] = "SAPI_VLOG_LEVEL";
|
||||||
|
char* sapi_vlog = getenv(kSapiVlogLevel);
|
||||||
|
if (sapi_vlog && strlen(sapi_vlog) > 0) {
|
||||||
|
envp->push_back(absl::StrCat(kSapiVlogLevel, "=", sapi_vlog));
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_RAW_VLOG(1, "Will execute args:['%s'], environment:['%s']",
|
||||||
|
absl::StrJoin(*args, "', '"), absl::StrJoin(*envp, "', '"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RunInitProcess(int signaling_fd, std::set<int> open_fds) {
|
||||||
|
// Spawn a child process and wait until it is dead.
|
||||||
|
pid_t child = fork();
|
||||||
|
if (child < 0) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "Could not spawn init process");
|
||||||
|
} else if (child == 0) {
|
||||||
|
// Send our PID (the actual sandboxee process) via SCM_CREDENTIALS.
|
||||||
|
struct msghdr msgh {};
|
||||||
|
struct iovec iov {};
|
||||||
|
msgh.msg_name = nullptr;
|
||||||
|
msgh.msg_namelen = 0;
|
||||||
|
|
||||||
|
msgh.msg_iov = &iov;
|
||||||
|
msgh.msg_iovlen = 1;
|
||||||
|
int data = 1;
|
||||||
|
iov.iov_base = &data;
|
||||||
|
iov.iov_len = sizeof(int);
|
||||||
|
msgh.msg_control = nullptr;
|
||||||
|
msgh.msg_controllen = 0;
|
||||||
|
SAPI_RAW_CHECK(sendmsg(signaling_fd, &msgh, 0), "Sending child PID");
|
||||||
|
return;
|
||||||
|
} else if (child > 0) {
|
||||||
|
// Perform some sanitization (basically equals to SanitizeEnvironment
|
||||||
|
// except that it does not require /proc to be available).
|
||||||
|
setsid();
|
||||||
|
if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "prctl(PR_SET_PDEATHSIG, SIGKILL) failed");
|
||||||
|
}
|
||||||
|
for (const auto& fd : open_fds) {
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply seccomp.
|
||||||
|
struct sock_filter code[] = {
|
||||||
|
LOAD_ARCH,
|
||||||
|
JNE32(Syscall::GetHostAuditArch(), DENY),
|
||||||
|
|
||||||
|
LOAD_SYSCALL_NR,
|
||||||
|
#ifdef __NR_waitpid
|
||||||
|
SYSCALL(__NR_waitpid, ALLOW),
|
||||||
|
#endif
|
||||||
|
SYSCALL(__NR_wait4, ALLOW),
|
||||||
|
SYSCALL(__NR_exit, ALLOW),
|
||||||
|
SYSCALL(__NR_exit_group, ALLOW),
|
||||||
|
DENY,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sock_fprog prog {};
|
||||||
|
prog.len = ABSL_ARRAYSIZE(code);
|
||||||
|
prog.filter = code;
|
||||||
|
|
||||||
|
SAPI_RAW_CHECK(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0,
|
||||||
|
"Denying new privs");
|
||||||
|
SAPI_RAW_CHECK(prctl(PR_SET_KEEPCAPS, 0) == 0, "Dropping caps");
|
||||||
|
SAPI_RAW_CHECK(syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
|
||||||
|
SECCOMP_FILTER_FLAG_TSYNC,
|
||||||
|
reinterpret_cast<uintptr_t>(&prog)) == 0,
|
||||||
|
"Enabling seccomp filter");
|
||||||
|
|
||||||
|
pid_t pid;
|
||||||
|
int status = 0;
|
||||||
|
|
||||||
|
// Reap children.
|
||||||
|
while (true) {
|
||||||
|
// Wait until we don't have any children anymore.
|
||||||
|
// We cannot watch for the child pid as ptrace steals our waitpid
|
||||||
|
// notifications. (See man ptrace / man waitpid).
|
||||||
|
pid = waitpid(-1, &status, 0);
|
||||||
|
if (pid < 0) {
|
||||||
|
if (errno == ECHILD) {
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForkServer::LaunchChild(const ForkRequest& request, int execve_fd,
|
||||||
|
int client_fd, uid_t uid, gid_t gid,
|
||||||
|
int user_ns_fd, int signaling_fd) {
|
||||||
|
bool will_execve = (request.mode() == FORKSERVER_FORK_EXECVE ||
|
||||||
|
request.mode() == FORKSERVER_FORK_EXECVE_SANDBOX);
|
||||||
|
|
||||||
|
if (request.mode() == FORKSERVER_FORK_JOIN_SANDBOX_UNWIND) {
|
||||||
|
SAPI_RAW_CHECK(setns(user_ns_fd, CLONE_NEWUSER) == 0,
|
||||||
|
"Could not join user NS");
|
||||||
|
close(user_ns_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the arguments before sandboxing (if needed), as doing it after
|
||||||
|
// sandoxing can cause syscall violations (e.g. related to memory management).
|
||||||
|
std::vector<std::string> args;
|
||||||
|
std::vector<std::string> envs;
|
||||||
|
const char** argv = nullptr;
|
||||||
|
const char** envp = nullptr;
|
||||||
|
if (will_execve) {
|
||||||
|
PrepareExecveArgs(request, &args, &envs);
|
||||||
|
}
|
||||||
|
|
||||||
|
SanitizeEnvironment(client_fd);
|
||||||
|
|
||||||
|
std::set<int> open_fds;
|
||||||
|
if (!sanitizer::GetListOfFDs(&open_fds)) {
|
||||||
|
SAPI_RAW_LOG(WARNING, "Could not get list of current open FDs");
|
||||||
|
}
|
||||||
|
InitializeNamespaces(request, uid, gid);
|
||||||
|
|
||||||
|
auto caps = cap_init();
|
||||||
|
for (auto cap : request.capabilities()) {
|
||||||
|
SAPI_RAW_CHECK(cap_set_flag(caps, CAP_PERMITTED, 1, &cap, CAP_SET) == 0,
|
||||||
|
"setting capability %d", cap);
|
||||||
|
SAPI_RAW_CHECK(cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_SET) == 0,
|
||||||
|
"setting capability %d", cap);
|
||||||
|
SAPI_RAW_CHECK(cap_set_flag(caps, CAP_INHERITABLE, 1, &cap, CAP_SET) == 0,
|
||||||
|
"setting capability %d", cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
SAPI_RAW_CHECK(cap_set_proc(caps) == 0, "while dropping capabilities");
|
||||||
|
cap_free(caps);
|
||||||
|
|
||||||
|
// The unwind sandbox is not running in a PID namespace and doesn't require
|
||||||
|
// an init process, everything else does.
|
||||||
|
if (request.mode() != FORKSERVER_FORK_JOIN_SANDBOX_UNWIND) {
|
||||||
|
RunInitProcess(signaling_fd, open_fds);
|
||||||
|
}
|
||||||
|
if (request.mode() == FORKSERVER_FORK_EXECVE_SANDBOX ||
|
||||||
|
request.mode() == FORKSERVER_FORK_JOIN_SANDBOX_UNWIND) {
|
||||||
|
// Sandboxing can be enabled either here - just before execve, or somewhere
|
||||||
|
// inside the executed binary (e.g. after basic structures have been
|
||||||
|
// initialized, and resources acquired). In the latter case, it's up to the
|
||||||
|
// sandboxed binary to establish proper Comms channel (using
|
||||||
|
// Comms::kSandbox2ClientCommsFD) and call sandbox2::Client::SandboxMeHere()
|
||||||
|
|
||||||
|
// Create a Comms object here and not above, as we know we will execve and
|
||||||
|
// therefore not call the Comms destructor, which would otherwise close the
|
||||||
|
// comms file descriptor, which we do not want for the general case.
|
||||||
|
Comms client_comms(Comms::kSandbox2ClientCommsFD);
|
||||||
|
Client c(&client_comms);
|
||||||
|
|
||||||
|
// The following client calls are basically SandboxMeHere. We split it so
|
||||||
|
// that we can set up the envp after we received the file descriptors but
|
||||||
|
// before we enable the syscall filter.
|
||||||
|
c.PrepareEnvironment();
|
||||||
|
|
||||||
|
envs.push_back(c.GetFdMapEnvVar());
|
||||||
|
// Convert argv and envs to const char **. No need to free it, as the
|
||||||
|
// code will either execve() or exit().
|
||||||
|
argv = util::VecStringToCharPtrArr(args);
|
||||||
|
envp = util::VecStringToCharPtrArr(envs);
|
||||||
|
|
||||||
|
c.EnableSandbox();
|
||||||
|
if (request.mode() == FORKSERVER_FORK_JOIN_SANDBOX_UNWIND) {
|
||||||
|
UnwindSetup pb_setup;
|
||||||
|
if (!client_comms.RecvProtoBuf(&pb_setup)) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string data = pb_setup.regs();
|
||||||
|
InstallUserRegs(data.c_str(), data.length());
|
||||||
|
ArmPtraceEmulation();
|
||||||
|
RunLibUnwindAndSymbolizer(pb_setup.pid(), &client_comms,
|
||||||
|
pb_setup.default_max_frames(),
|
||||||
|
pb_setup.delim());
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
ExecuteProcess(execve_fd, argv, envp);
|
||||||
|
}
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (will_execve) {
|
||||||
|
argv = util::VecStringToCharPtrArr(args);
|
||||||
|
envp = util::VecStringToCharPtrArr(envs);
|
||||||
|
ExecuteProcess(execve_fd, argv, envp);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t ForkServer::ServeRequest() const {
|
||||||
|
ForkRequest fork_request;
|
||||||
|
if (!comms_->RecvProtoBuf(&fork_request)) {
|
||||||
|
if (comms_->IsTerminated()) {
|
||||||
|
SAPI_RAW_VLOG(1, "ForkServer Comms closed. Exiting");
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
SAPI_RAW_LOG(FATAL, "Failed to receive ForkServer request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int comms_fd;
|
||||||
|
if (!comms_->RecvFD(&comms_fd)) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "Failed to receive Comms FD");
|
||||||
|
}
|
||||||
|
|
||||||
|
int exec_fd = -1;
|
||||||
|
if (fork_request.mode() == FORKSERVER_FORK_EXECVE ||
|
||||||
|
fork_request.mode() == FORKSERVER_FORK_EXECVE_SANDBOX) {
|
||||||
|
if (!comms_->RecvFD(&exec_fd)) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "Failed to receive Exec FD");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the kernel notify us with SIGCHLD when the process terminates.
|
||||||
|
// We use sigaction(SIGCHLD, flags=SA_NOCLDWAIT) in combination with
|
||||||
|
// this to make sure the zombie process is reaped immediately.
|
||||||
|
int clone_flags = fork_request.clone_flags() | SIGCHLD;
|
||||||
|
|
||||||
|
int user_ns_fd = -1;
|
||||||
|
if (fork_request.mode() == FORKSERVER_FORK_JOIN_SANDBOX_UNWIND) {
|
||||||
|
if (!comms_->RecvFD(&user_ns_fd)) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "Failed to receive user namespace fd");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store uid and gid since they will change if CLONE_NEWUSER is set.
|
||||||
|
uid_t uid = getuid();
|
||||||
|
uid_t gid = getgid();
|
||||||
|
|
||||||
|
int socketpair_fds[2];
|
||||||
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socketpair_fds)) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "socketpair()");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
int val = 1;
|
||||||
|
if (setsockopt(socketpair_fds[i], SOL_SOCKET, SO_PASSCRED, &val,
|
||||||
|
sizeof(val))) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "setsockopt failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_util::fileops::FDCloser fd_closer0{socketpair_fds[0]};
|
||||||
|
file_util::fileops::FDCloser fd_closer1{socketpair_fds[1]};
|
||||||
|
|
||||||
|
pid_t sandboxee_pid = util::ForkWithFlags(clone_flags);
|
||||||
|
// Note: init_pid will be overwritten with the actual init pid if the init
|
||||||
|
// process was started or stays at 0 if that is not needed (custom
|
||||||
|
// forkserver).
|
||||||
|
pid_t init_pid = 0;
|
||||||
|
if (sandboxee_pid == -1) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "util::ForkWithFlags(%x)", clone_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child.
|
||||||
|
if (sandboxee_pid == 0) {
|
||||||
|
LaunchChild(fork_request, exec_fd, comms_fd, uid, gid, user_ns_fd,
|
||||||
|
fd_closer1.get());
|
||||||
|
|
||||||
|
// comms_fd has been remapped to 1023 (kSandbox2ClientCommsFD), so the
|
||||||
|
// original FD is not required anymore.
|
||||||
|
if (comms_fd != Comms::kSandbox2ClientCommsFD) {
|
||||||
|
close(comms_fd);
|
||||||
|
}
|
||||||
|
return sandboxee_pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_closer1.Close();
|
||||||
|
|
||||||
|
if (fork_request.mode() != FORKSERVER_FORK_JOIN_SANDBOX_UNWIND) {
|
||||||
|
union {
|
||||||
|
struct cmsghdr cmh;
|
||||||
|
char ctrl[CMSG_SPACE(sizeof(struct ucred))];
|
||||||
|
} test_msg{};
|
||||||
|
|
||||||
|
struct msghdr msgh {};
|
||||||
|
struct iovec iov {};
|
||||||
|
|
||||||
|
msgh.msg_iov = &iov;
|
||||||
|
msgh.msg_iovlen = 1;
|
||||||
|
msgh.msg_control = test_msg.ctrl;
|
||||||
|
msgh.msg_controllen = sizeof(test_msg);
|
||||||
|
|
||||||
|
int data = 0;
|
||||||
|
iov.iov_base = &data;
|
||||||
|
iov.iov_len = sizeof(int);
|
||||||
|
|
||||||
|
// The pid of the init process is equal to the child process that we've
|
||||||
|
// previously forked.
|
||||||
|
init_pid = sandboxee_pid;
|
||||||
|
|
||||||
|
// And the actual sandboxee will be forked from the init process, so we need
|
||||||
|
// to receive the actual PID.
|
||||||
|
struct cmsghdr* cmsgp = nullptr;
|
||||||
|
if (TEMP_FAILURE_RETRY(recvmsg(fd_closer0.get(), &msgh, MSG_WAITALL)) <=
|
||||||
|
0 ||
|
||||||
|
!(cmsgp = CMSG_FIRSTHDR(&msgh)) || /* Assigning here on purpose */
|
||||||
|
cmsgp->cmsg_len != CMSG_LEN(sizeof(struct ucred)) ||
|
||||||
|
cmsgp->cmsg_level != SOL_SOCKET ||
|
||||||
|
cmsgp->cmsg_type != SCM_CREDENTIALS) {
|
||||||
|
SAPI_RAW_LOG(ERROR, "Receiving sandboxee pid failed");
|
||||||
|
sandboxee_pid = -1;
|
||||||
|
kill(init_pid, SIGKILL);
|
||||||
|
} else {
|
||||||
|
struct ucred* ucredp = reinterpret_cast<struct ucred*>(CMSG_DATA(cmsgp));
|
||||||
|
sandboxee_pid = ucredp->pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Parent.
|
||||||
|
close(comms_fd);
|
||||||
|
if (exec_fd >= 0) {
|
||||||
|
close(exec_fd);
|
||||||
|
}
|
||||||
|
if (user_ns_fd >= 0) {
|
||||||
|
close(user_ns_fd);
|
||||||
|
}
|
||||||
|
if (!comms_->SendInt32(init_pid)) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "Failed to send init PID: %d", init_pid);
|
||||||
|
}
|
||||||
|
if (!comms_->SendInt32(sandboxee_pid)) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "Failed to send sandboxee PID: %d", sandboxee_pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sandboxee_pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ForkServer::Initialize() {
|
||||||
|
// If the parent goes down, so should we.
|
||||||
|
if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "prctl(PR_SET_PDEATHSIG, SIGKILL)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All processes spawned by the fork'd/execute'd process will see this process
|
||||||
|
// as /sbin/init. Therefore it will receive (and ignore) their final status
|
||||||
|
// (see the next comment as well). PR_SET_CHILD_SUBREAPER is available since
|
||||||
|
// kernel version 3.4, so don't panic if it fails.
|
||||||
|
if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == -1) {
|
||||||
|
SAPI_RAW_VLOG(3, "prctl(PR_SET_CHILD_SUBREAPER, 1): %s [%d]",
|
||||||
|
StrError(errno), errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't convert terminated child processes into zombies. It's up to the
|
||||||
|
// sandbox (Monitor) to track them and receive/report their final status.
|
||||||
|
struct sigaction sa;
|
||||||
|
sa.sa_handler = SIG_DFL;
|
||||||
|
sa.sa_flags = SA_NOCLDWAIT;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
if (sigaction(SIGCHLD, &sa, nullptr) == -1) {
|
||||||
|
SAPI_RAW_PLOG(ERROR, "sigaction(SIGCHLD, flags=SA_NOCLDWAIT)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForkServer::SanitizeEnvironment(int client_fd) {
|
||||||
|
// Duplicate client's CommsFD onto fd=Comms::kSandbox2ClientCommsFD (1023).
|
||||||
|
SAPI_RAW_CHECK(dup2(client_fd, Comms::kSandbox2ClientCommsFD) != -1,
|
||||||
|
"while remapping client comms fd");
|
||||||
|
// Mark all file descriptors, except the standard ones (needed
|
||||||
|
// for proper sandboxed process operations), as close-on-exec.
|
||||||
|
if (!sanitizer::SanitizeCurrentProcess(
|
||||||
|
{STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO,
|
||||||
|
Comms::kSandbox2ClientCommsFD},
|
||||||
|
/* close_fds = */ false)) {
|
||||||
|
SAPI_RAW_LOG(FATAL, "sanitizer::SanitizeCurrentProcess(close_fds=false)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForkServer::ExecuteProcess(int execve_fd, const char** argv,
|
||||||
|
const char** envp) {
|
||||||
|
// Do not add any code before execve(), as it's subject to seccomp policies.
|
||||||
|
// Indicate that it's a special execve(), by setting 4th, 5th and 6th syscall
|
||||||
|
// argument to magic values.
|
||||||
|
util::Syscall(
|
||||||
|
__NR_execveat, static_cast<uintptr_t>(execve_fd),
|
||||||
|
reinterpret_cast<uintptr_t>(""), reinterpret_cast<uintptr_t>(argv),
|
||||||
|
reinterpret_cast<uintptr_t>(envp), static_cast<uintptr_t>(AT_EMPTY_PATH),
|
||||||
|
reinterpret_cast<uintptr_t>(internal::kExecveMagic));
|
||||||
|
|
||||||
|
int saved_errno = errno;
|
||||||
|
SAPI_RAW_PLOG(ERROR, "sandbox2::ForkServer: execveat failed");
|
||||||
|
|
||||||
|
if (saved_errno == ENOSYS) {
|
||||||
|
SAPI_RAW_LOG(ERROR,
|
||||||
|
"sandbox2::ForkServer: This is likely caused by running"
|
||||||
|
" sandbox2 on too old a kernel."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
util::Syscall(__NR_exit_group, EXIT_FAILURE);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForkServer::InitializeNamespaces(const ForkRequest& request, uid_t uid,
|
||||||
|
gid_t gid) {
|
||||||
|
if (!request.has_mount_tree()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int32_t clone_flags = request.clone_flags();
|
||||||
|
if (request.mode() == FORKSERVER_FORK_JOIN_SANDBOX_UNWIND) {
|
||||||
|
clone_flags = CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC;
|
||||||
|
SAPI_RAW_PCHECK(!unshare(clone_flags),
|
||||||
|
"Could not create new namespaces for libunwind");
|
||||||
|
}
|
||||||
|
Namespace::InitializeNamespaces(
|
||||||
|
uid, gid, clone_flags, Mounts(request.mount_tree()),
|
||||||
|
request.mode() != FORKSERVER_FORK_JOIN_SANDBOX_UNWIND,
|
||||||
|
request.hostname());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
109
sandboxed_api/sandbox2/forkserver.h
Normal file
109
sandboxed_api/sandbox2/forkserver.h
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// sandbox2::ForkServer is a class which serves fork()ing request for the
|
||||||
|
// clients.
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX2_FORKSERVER_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX2_FORKSERVER_H_
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "absl/synchronization/mutex.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
class Comms;
|
||||||
|
class ForkRequest;
|
||||||
|
|
||||||
|
// Envvar indicating that this process should not start the fork-server.
|
||||||
|
static constexpr const char* kForkServerDisableEnv = "SANDBOX2_NOFORKSERVER";
|
||||||
|
|
||||||
|
class ForkClient {
|
||||||
|
public:
|
||||||
|
ForkClient(const ForkClient&) = delete;
|
||||||
|
ForkClient& operator=(const ForkClient&) = delete;
|
||||||
|
|
||||||
|
explicit ForkClient(Comms* comms) : comms_(comms) {}
|
||||||
|
|
||||||
|
// Sends the fork request over the supplied Comms channel.
|
||||||
|
pid_t SendRequest(const ForkRequest& request, int exec_fd, int comms_fd,
|
||||||
|
int user_ns_fd = -1, pid_t* init_pid = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Comms channel connecting with the ForkServer. Not owned by the object.
|
||||||
|
Comms* comms_;
|
||||||
|
// Mutex locking transactions (requests) over the Comms channel.
|
||||||
|
absl::Mutex comms_mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ForkServer {
|
||||||
|
public:
|
||||||
|
ForkServer(const ForkServer&) = delete;
|
||||||
|
ForkServer& operator=(const ForkServer&) = delete;
|
||||||
|
|
||||||
|
explicit ForkServer(Comms* comms) : comms_(comms) {
|
||||||
|
if (!Initialize()) {
|
||||||
|
LOG(FATAL) << "Could not initialize the ForkServer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receives a fork request from the master process. The started process does
|
||||||
|
// not need to be waited for (with waitid/waitpid/wait3/wait4) as the current
|
||||||
|
// process will have the SIGCHLD set to sa_flags=SA_NOCLDWAIT.
|
||||||
|
// Returns values defined as with fork() (-1 means error).
|
||||||
|
pid_t ServeRequest() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Analyzes the PB received, and execute the process. If kept_fds is
|
||||||
|
// non-nullptr, it specifies a list of file descriptors to be kept open after
|
||||||
|
// sanitization call is done, the remaining file descriptors will be closed.
|
||||||
|
static void LaunchChild(const ForkRequest& request, int execve_fd,
|
||||||
|
int client_fd, uid_t uid, gid_t gid, int user_ns_fd,
|
||||||
|
int signaling_fd);
|
||||||
|
|
||||||
|
// Prepares the Fork-Server (worker side, not the requester side) for work by
|
||||||
|
// sanitizing the environment:
|
||||||
|
// - go down if the parent goes down,
|
||||||
|
// - become subreaper - PR_SET_CHILD_SUBREAPER (man prctl),
|
||||||
|
// - don't convert children processes into zombies if they terminate.
|
||||||
|
static bool Initialize();
|
||||||
|
|
||||||
|
// Prepares arguments for the upcoming execve (if execve was requested).
|
||||||
|
static void PrepareExecveArgs(const ForkRequest& request,
|
||||||
|
std::vector<std::string>* args,
|
||||||
|
std::vector<std::string>* envp);
|
||||||
|
|
||||||
|
// Ensures that no unnecessary file descriptors are lingering after execve().
|
||||||
|
static void SanitizeEnvironment(int client_fd);
|
||||||
|
|
||||||
|
// Executes the sandboxee, or exit with Executor::kFailedExecve.
|
||||||
|
static void ExecuteProcess(int execve_fd, const char** argv,
|
||||||
|
const char** envp);
|
||||||
|
|
||||||
|
// Runs namespace initializers for a sandboxee.
|
||||||
|
static void InitializeNamespaces(const ForkRequest& request, uid_t uid,
|
||||||
|
gid_t gid);
|
||||||
|
|
||||||
|
// Comms channel which is used to send requests to this class. Not owned by
|
||||||
|
// the object.
|
||||||
|
Comms* comms_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX2_FORKSERVER_H_
|
53
sandboxed_api/sandbox2/forkserver.proto
Normal file
53
sandboxed_api/sandbox2/forkserver.proto
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// A proto for the sandbox2::Forkserver class
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
package sandbox2;
|
||||||
|
|
||||||
|
import "sandboxed_api/sandbox2/mounttree.proto";
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
// Fork, execve and sandbox
|
||||||
|
FORKSERVER_FORK_EXECVE_SANDBOX = 1;
|
||||||
|
// Fork and execve, but no sandboxing
|
||||||
|
FORKSERVER_FORK_EXECVE = 2;
|
||||||
|
// Just fork
|
||||||
|
FORKSERVER_FORK = 3;
|
||||||
|
// Special internal case: join a user namespace prior to unwinding
|
||||||
|
FORKSERVER_FORK_JOIN_SANDBOX_UNWIND = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ForkRequest {
|
||||||
|
// List of arguments, starting with argv[0]
|
||||||
|
repeated bytes args = 1;
|
||||||
|
// List of environment variables which will be passed to the child
|
||||||
|
repeated bytes envs = 2;
|
||||||
|
|
||||||
|
// How to interpret the request
|
||||||
|
required Mode mode = 3;
|
||||||
|
|
||||||
|
// Clone flags for the new process
|
||||||
|
optional int32 clone_flags = 4 [default = 0];
|
||||||
|
|
||||||
|
// Capabilities to keep when starting the sandboxee
|
||||||
|
repeated int32 capabilities = 5;
|
||||||
|
|
||||||
|
// The mount tree used for namespace initialization
|
||||||
|
optional MountTree mount_tree = 6;
|
||||||
|
|
||||||
|
// Hostname in the network namespace
|
||||||
|
optional bytes hostname = 7;
|
||||||
|
}
|
122
sandboxed_api/sandbox2/forkserver_test.cc
Normal file
122
sandboxed_api/sandbox2/forkserver_test.cc
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.pb.h"
|
||||||
|
#include "sandboxed_api/sandbox2/global_forkclient.h"
|
||||||
|
#include "sandboxed_api/sandbox2/ipc.h"
|
||||||
|
#include "sandboxed_api/sandbox2/testing.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
class IpcPeer {
|
||||||
|
public:
|
||||||
|
explicit IpcPeer(IPC* ipc) : ipc_{ipc} {}
|
||||||
|
|
||||||
|
void SetUpServerSideComms(int fd) { ipc_->SetUpServerSideComms(fd); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
IPC* ipc_;
|
||||||
|
};
|
||||||
|
|
||||||
|
int GetMinimalTestcaseFd() {
|
||||||
|
const std::string path = GetTestSourcePath("sandbox2/testcases/minimal");
|
||||||
|
return open(path.c_str(), O_RDONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t TestSingleRequest(Mode mode, int exec_fd, int userns_fd) {
|
||||||
|
ForkRequest fork_req;
|
||||||
|
IPC ipc;
|
||||||
|
int sv[2];
|
||||||
|
// Setup IPC
|
||||||
|
PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != -1);
|
||||||
|
IpcPeer{&ipc}.SetUpServerSideComms(sv[1]);
|
||||||
|
// Setup fork_req
|
||||||
|
fork_req.set_mode(mode);
|
||||||
|
fork_req.add_args("/binary");
|
||||||
|
fork_req.add_envs("FOO=1");
|
||||||
|
|
||||||
|
pid_t pid =
|
||||||
|
GetGlobalForkClient()->SendRequest(fork_req, exec_fd, sv[0], userns_fd);
|
||||||
|
if (pid != -1) {
|
||||||
|
VLOG(1) << "TestSingleRequest: Waiting for pid=" << pid;
|
||||||
|
waitpid(pid, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sv[0]);
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForkserverTest, SimpleFork) {
|
||||||
|
// Make sure that the regular fork request works.
|
||||||
|
ASSERT_NE(TestSingleRequest(FORKSERVER_FORK, -1, -1), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForkserverTest, SimpleForkNoZombie) {
|
||||||
|
// Make sure that we don't create zombies.
|
||||||
|
pid_t child = TestSingleRequest(FORKSERVER_FORK, -1, -1);
|
||||||
|
ASSERT_NE(child, -1);
|
||||||
|
std::string proc = absl::StrCat("/proc/", child, "/cmdline");
|
||||||
|
|
||||||
|
// Give the kernel some time to clean up.
|
||||||
|
// Poll every 10ms up to 500 times (5s)
|
||||||
|
bool process_reaped = false;
|
||||||
|
for (int i = 0; i < 500; i++) {
|
||||||
|
if (access(proc.c_str(), F_OK) == -1) {
|
||||||
|
process_reaped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(10 * 1000); // 10 ms
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(process_reaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForkserverTest, ForkExecveWorks) {
|
||||||
|
// Run a test binary through the FORK_EXECVE request.
|
||||||
|
int exec_fd = GetMinimalTestcaseFd();
|
||||||
|
PCHECK(exec_fd != -1) << "Could not open test binary";
|
||||||
|
ASSERT_NE(TestSingleRequest(FORKSERVER_FORK_EXECVE, exec_fd, -1), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForkserverTest, ForkExecveSandboxWithoutPolicy) {
|
||||||
|
// Run a test binary through the FORKSERVER_FORK_EXECVE_SANDBOX request.
|
||||||
|
int exec_fd = GetMinimalTestcaseFd();
|
||||||
|
PCHECK(exec_fd != -1) << "Could not open test binary";
|
||||||
|
ASSERT_NE(TestSingleRequest(FORKSERVER_FORK_EXECVE_SANDBOX, exec_fd, -1), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForkserverTest, ForkExecveRequiresExecFD) {
|
||||||
|
// This test should generate some warnings:
|
||||||
|
// (Sending Exec FD (-1) to the ForkServer failed).
|
||||||
|
ASSERT_EQ(TestSingleRequest(FORKSERVER_FORK_EXECVE, -1, -1), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ForkserverTest, ForkExecveSandboxRequiresExecFD) {
|
||||||
|
// This test should generate some warnings:
|
||||||
|
// (Sending Exec FD (-1) to the ForkServer failed).
|
||||||
|
ASSERT_EQ(TestSingleRequest(FORKSERVER_FORK_EXECVE_SANDBOX, -1, -1), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
135
sandboxed_api/sandbox2/global_forkclient.cc
Normal file
135
sandboxed_api/sandbox2/global_forkclient.cc
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Implementation of the sandbox2::ForkServer class.
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/global_forkclient.h"
|
||||||
|
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "absl/base/attributes.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "sandboxed_api/sandbox2/comms.h"
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.h"
|
||||||
|
#include "sandboxed_api/sandbox2/sanitizer.h"
|
||||||
|
#include "sandboxed_api/sandbox2/util/strerror.h"
|
||||||
|
#include "sandboxed_api/util/raw_logging.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
// Global ForkClient object linking with the global ForkServer.
|
||||||
|
static ForkClient* global_fork_client = nullptr;
|
||||||
|
static pid_t global_fork_server_pid = -1;
|
||||||
|
|
||||||
|
ForkClient* GetGlobalForkClient() {
|
||||||
|
SAPI_RAW_CHECK(global_fork_client != nullptr,
|
||||||
|
"global fork client not initialized");
|
||||||
|
return global_fork_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t GetGlobalForkServerPid() { return global_fork_server_pid; }
|
||||||
|
|
||||||
|
static void StartGlobalForkServer() {
|
||||||
|
SAPI_RAW_CHECK(global_fork_client == nullptr,
|
||||||
|
"global fork server already initialized");
|
||||||
|
if (getenv(kForkServerDisableEnv)) {
|
||||||
|
SAPI_RAW_VLOG(1,
|
||||||
|
"Start of the Global Fork-Server prevented by the '%s' "
|
||||||
|
"environment variable present",
|
||||||
|
kForkServerDisableEnv);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizer::WaitForTsan();
|
||||||
|
|
||||||
|
// We should be really single-threaded now, as it's the point of the whole
|
||||||
|
// exercise.
|
||||||
|
int num_threads = sanitizer::GetNumberOfThreads(getpid());
|
||||||
|
if (num_threads != 1) {
|
||||||
|
SAPI_RAW_LOG(ERROR,
|
||||||
|
"BADNESS MAY HAPPEN. ForkServer::Init() created in a "
|
||||||
|
"multi-threaded context, %d threads present",
|
||||||
|
num_threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sv[2];
|
||||||
|
SAPI_RAW_CHECK(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) != -1,
|
||||||
|
"creating socket pair");
|
||||||
|
|
||||||
|
// Fork the fork-server, and clean-up the resources (close remote sockets).
|
||||||
|
pid_t pid = fork();
|
||||||
|
SAPI_RAW_PCHECK(pid != -1, "during fork");
|
||||||
|
|
||||||
|
// Parent.
|
||||||
|
if (pid > 0) {
|
||||||
|
close(sv[0]);
|
||||||
|
global_fork_client = new ForkClient{new Comms{sv[1]}};
|
||||||
|
global_fork_server_pid = pid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the logs go stderr.
|
||||||
|
google::LogToStderr();
|
||||||
|
|
||||||
|
// Child.
|
||||||
|
close(sv[1]);
|
||||||
|
|
||||||
|
// Make the process' name easily recognizable with ps/pstree.
|
||||||
|
if (prctl(PR_SET_NAME, "S2-FORK-SERV", 0, 0, 0) != 0) {
|
||||||
|
SAPI_RAW_PLOG(WARNING, "prctl(PR_SET_NAME, 'S2-FORK-SERV')");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't react (with stack-tracing) to SIGTERM's sent from other processes
|
||||||
|
// (e.g. from the borglet or SubProcess). This ForkServer should go down if
|
||||||
|
// the parent goes down (or if the GlobalForkServerComms is closed), which is
|
||||||
|
// assured by prctl(PR_SET_PDEATHSIG, SIGKILL) being called in the
|
||||||
|
// ForkServer::Initialize(). We don't want to change behavior of non-global
|
||||||
|
// ForkServers, hence it's called here and not in the
|
||||||
|
// ForkServer::Initialize().
|
||||||
|
struct sigaction sa;
|
||||||
|
sa.sa_handler = SIG_IGN;
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
if (sigaction(SIGTERM, &sa, nullptr) == -1) {
|
||||||
|
SAPI_RAW_PLOG(WARNING, "sigaction(SIGTERM, sa_handler=SIG_IGN)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Comms comms(sv[0]);
|
||||||
|
ForkServer fork_server(&comms);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
pid_t child_pid = fork_server.ServeRequest();
|
||||||
|
if (!child_pid) {
|
||||||
|
// FORKSERVER_FORK sent to the global forkserver. This case does not make
|
||||||
|
// sense, we thus kill the process here.
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
// Run the ForkServer from the constructor, when no other threads are present.
|
||||||
|
// Because it's possible to start thread-inducing initializers before
|
||||||
|
// RunInitializers() (base/googleinit.h) it's not enough to just register
|
||||||
|
// a 0000_<name> initializer instead.
|
||||||
|
ABSL_ATTRIBUTE_UNUSED
|
||||||
|
__attribute__((constructor)) static void StartSandbox2Forkserver() {
|
||||||
|
sandbox2::StartGlobalForkServer();
|
||||||
|
}
|
32
sandboxed_api/sandbox2/global_forkclient.h
Normal file
32
sandboxed_api/sandbox2/global_forkclient.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2019 Google LLC. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// StartGlobalForkServer() is called early in a process using a constructor, so
|
||||||
|
// it can fork() safely (in a single-threaded context)
|
||||||
|
|
||||||
|
#ifndef SANDBOXED_API_SANDBOX2_GLOBAL_FORKCLIENT_H_
|
||||||
|
#define SANDBOXED_API_SANDBOX2_GLOBAL_FORKCLIENT_H_
|
||||||
|
|
||||||
|
#include "sandboxed_api/sandbox2/forkserver.h"
|
||||||
|
|
||||||
|
namespace sandbox2 {
|
||||||
|
|
||||||
|
ForkClient* GetGlobalForkClient();
|
||||||
|
|
||||||
|
// Returns the fork server PID. Used in tests.
|
||||||
|
pid_t GetGlobalForkServerPid();
|
||||||
|
|
||||||
|
} // namespace sandbox2
|
||||||
|
|
||||||
|
#endif // SANDBOXED_API_SANDBOX2_GLOBAL_FORKCLIENT_H_
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user