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