Sandboxed API OSS release.

PiperOrigin-RevId: 238996664
Change-Id: I9646527e2be68ee0b6b371572b7aafe967102e57

Signed-off-by: Christian Blichmann <cblichmann@google.com>
This commit is contained in:
Christian Blichmann 2019-03-18 17:21:48 +01:00
commit 177b969e8c
No known key found for this signature in database
GPG Key ID: 1ADBE004E6F49F6D
252 changed files with 35889 additions and 0 deletions

4
.clang-format Normal file
View File

@ -0,0 +1,4 @@
---
Language: Cpp
BasedOnStyle: Google
...

25
CONTRIBUTING.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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",
],
)

View 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
)

View 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"],
)

View 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"],
)

View 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"],
)

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

File diff suppressed because it is too large Load Diff

View 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;
}

View 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

View 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
)

View 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,
)

View 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' > $@
""",
)

Binary file not shown.

93
sandboxed_api/call.h Normal file
View 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
View 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";
}

View 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).

View 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).

View 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).

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View 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).

View 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.

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

View 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_

View 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",
],
)

View 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"],
)

View 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_

View 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); }

View 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;
}
}

View 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

View 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",
],
)

View 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"],
)

View 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_

View 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);
}

View 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();
}

View 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];
}

View 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;
}

View 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",
],
)

View 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
View 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_

View 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_

View 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;
}

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

View 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
View 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
View 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_

View 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",
],
)

View 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.

View 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

View 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_

View 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

View 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_

View 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

View 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

View 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_

View 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

View 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_

View 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

View 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;
};

View 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;
}

View 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).

View 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]`.

View 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.

View 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.

View 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",
],
)

View 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;
}

View 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;
}

View 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

View 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",
],
)

View File

@ -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);
}

View 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.
// 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;
}

View 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,
)

View 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;
}

View 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;
}

View 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",
],
)

View 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;
}

View 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"],
)

View 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;
}
}

View 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;
}

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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;
}

View 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

View 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();
}

View 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