commit 177b969e8cf96b6254fa9195e4c501dc12a82de9 Author: Christian Blichmann Date: Mon Mar 18 17:21:48 2019 +0100 Sandboxed API OSS release. PiperOrigin-RevId: 238996664 Change-Id: I9646527e2be68ee0b6b371572b7aafe967102e57 Signed-off-by: Christian Blichmann diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..06ea346 --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +--- +Language: Cpp +BasedOnStyle: Google +... diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..21e4c71 --- /dev/null +++ b/CONTRIBUTING.md @@ -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). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..753fade --- /dev/null +++ b/README.md @@ -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). diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..4c5a724 --- /dev/null +++ b/WORKSPACE @@ -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 +) diff --git a/sandboxed_api/BUILD.bazel b/sandboxed_api/BUILD.bazel new file mode 100644 index 0000000..6bd022b --- /dev/null +++ b/sandboxed_api/BUILD.bazel @@ -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", + ], +) diff --git a/sandboxed_api/bazel/BUILD b/sandboxed_api/bazel/BUILD new file mode 100644 index 0000000..6a3351d --- /dev/null +++ b/sandboxed_api/bazel/BUILD @@ -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", + ], +) diff --git a/sandboxed_api/bazel/embed_data.bzl b/sandboxed_api/bazel/embed_data.bzl new file mode 100644 index 0000000..f6e23c9 --- /dev/null +++ b/sandboxed_api/bazel/embed_data.bzl @@ -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 + ) diff --git a/sandboxed_api/bazel/external/enum34.BUILD b/sandboxed_api/bazel/external/enum34.BUILD new file mode 100644 index 0000000..ba538fd --- /dev/null +++ b/sandboxed_api/bazel/external/enum34.BUILD @@ -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"], +) diff --git a/sandboxed_api/bazel/external/libcap.BUILD b/sandboxed_api/bazel/external/libcap.BUILD new file mode 100644 index 0000000..7436d7b --- /dev/null +++ b/sandboxed_api/bazel/external/libcap.BUILD @@ -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"], +) diff --git a/sandboxed_api/bazel/external/libffi.BUILD b/sandboxed_api/bazel/external/libffi.BUILD new file mode 100644 index 0000000..02374ad --- /dev/null +++ b/sandboxed_api/bazel/external/libffi.BUILD @@ -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"], +) diff --git a/sandboxed_api/bazel/external/libunwind.BUILD b/sandboxed_api/bazel/external/libunwind.BUILD new file mode 100644 index 0000000..9168ea2 --- /dev/null +++ b/sandboxed_api/bazel/external/libunwind.BUILD @@ -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, +]] diff --git a/sandboxed_api/bazel/external/six.BUILD b/sandboxed_api/bazel/external/six.BUILD new file mode 100644 index 0000000..3185e21 --- /dev/null +++ b/sandboxed_api/bazel/external/six.BUILD @@ -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"], +) diff --git a/sandboxed_api/bazel/external/zlib.BUILD b/sandboxed_api/bazel/external/zlib.BUILD new file mode 100644 index 0000000..4a49408 --- /dev/null +++ b/sandboxed_api/bazel/external/zlib.BUILD @@ -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"], +) diff --git a/sandboxed_api/bazel/external/zlib.patch b/sandboxed_api/bazel/external/zlib.patch new file mode 100644 index 0000000..dd1c32c --- /dev/null +++ b/sandboxed_api/bazel/external/zlib.patch @@ -0,0 +1,2118 @@ +diff -c zlib-1.2.11/adler32.c zlib-1.2.11-patched/adler32.c +*** zlib-1.2.11/adler32.c 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/adler32.c 2019-03-08 15:19:23.378545000 +0100 +*************** +*** 7,13 **** + + #include "zutil.h" + +! local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); + + #define BASE 65521U /* largest prime smaller than 65536 */ + #define NMAX 5552 +--- 7,13 ---- + + #include "zutil.h" + +! local uLong adler32_combine_ (uLong adler1, uLong adler2, z_off64_t len2); + + #define BASE 65521U /* largest prime smaller than 65536 */ + #define NMAX 5552 +Common subdirectories: zlib-1.2.11/amiga and zlib-1.2.11-patched/amiga +Common subdirectories: zlib-1.2.11/contrib and zlib-1.2.11-patched/contrib +diff -c zlib-1.2.11/crc32.c zlib-1.2.11-patched/crc32.c +*** zlib-1.2.11/crc32.c 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/crc32.c 2019-03-08 15:19:23.354545239 +0100 +*************** +*** 35,63 **** + # define BYFOUR + #endif + #ifdef BYFOUR +! local unsigned long crc32_little OF((unsigned long, +! const unsigned char FAR *, z_size_t)); +! local unsigned long crc32_big OF((unsigned long, +! const unsigned char FAR *, z_size_t)); + # define TBLS 8 + #else + # define TBLS 1 + #endif /* BYFOUR */ + + /* Local functions for crc concatenation */ +! local unsigned long gf2_matrix_times OF((unsigned long *mat, +! unsigned long vec)); +! local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); +! local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2)); + + + #ifdef DYNAMIC_CRC_TABLE + + local volatile int crc_table_empty = 1; + local z_crc_t FAR crc_table[TBLS][256]; +! local void make_crc_table OF((void)); + #ifdef MAKECRCH +! local void write_table OF((FILE *, const z_crc_t FAR *)); + #endif /* MAKECRCH */ + /* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: +--- 35,63 ---- + # define BYFOUR + #endif + #ifdef BYFOUR +! local unsigned long crc32_little (unsigned long, +! const unsigned char FAR *, z_size_t); +! local unsigned long crc32_big (unsigned long, +! const unsigned char FAR *, z_size_t); + # define TBLS 8 + #else + # define TBLS 1 + #endif /* BYFOUR */ + + /* Local functions for crc concatenation */ +! local unsigned long gf2_matrix_times (unsigned long *mat, +! unsigned long vec); +! local void gf2_matrix_square (unsigned long *square, unsigned long *mat); +! local uLong crc32_combine_ (uLong crc1, uLong crc2, z_off64_t len2); + + + #ifdef DYNAMIC_CRC_TABLE + + local volatile int crc_table_empty = 1; + local z_crc_t FAR crc_table[TBLS][256]; +! local void make_crc_table (void); + #ifdef MAKECRCH +! local void write_table (FILE *, const z_crc_t FAR *); + #endif /* MAKECRCH */ + /* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: +diff -c zlib-1.2.11/deflate.c zlib-1.2.11-patched/deflate.c +*** zlib-1.2.11/deflate.c 2017-01-15 18:29:40.000000000 +0100 +--- zlib-1.2.11-patched/deflate.c 2019-03-08 15:19:23.410544679 +0100 +*************** +*** 70,103 **** + finish_done /* finish done, accept no more input or output */ + } block_state; + +! typedef block_state (*compress_func) OF((deflate_state *s, int flush)); + /* Compression function. Returns the block state after the call. */ + +! local int deflateStateCheck OF((z_streamp strm)); +! local void slide_hash OF((deflate_state *s)); +! local void fill_window OF((deflate_state *s)); +! local block_state deflate_stored OF((deflate_state *s, int flush)); +! local block_state deflate_fast OF((deflate_state *s, int flush)); + #ifndef FASTEST +! local block_state deflate_slow OF((deflate_state *s, int flush)); + #endif +! local block_state deflate_rle OF((deflate_state *s, int flush)); +! local block_state deflate_huff OF((deflate_state *s, int flush)); +! local void lm_init OF((deflate_state *s)); +! local void putShortMSB OF((deflate_state *s, uInt b)); +! local void flush_pending OF((z_streamp strm)); +! local unsigned read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); + #ifdef ASMV + # pragma message("Assembler code may have bugs -- use at your own risk") +! void match_init OF((void)); /* asm code initialization */ +! uInt longest_match OF((deflate_state *s, IPos cur_match)); + #else +! local uInt longest_match OF((deflate_state *s, IPos cur_match)); + #endif + + #ifdef ZLIB_DEBUG +! local void check_match OF((deflate_state *s, IPos start, IPos match, +! int length)); + #endif + + /* =========================================================================== +--- 70,103 ---- + finish_done /* finish done, accept no more input or output */ + } block_state; + +! typedef block_state (*compress_func) (deflate_state *s, int flush); + /* Compression function. Returns the block state after the call. */ + +! local int deflateStateCheck (z_streamp strm); +! local void slide_hash (deflate_state *s); +! local void fill_window (deflate_state *s); +! local block_state deflate_stored (deflate_state *s, int flush); +! local block_state deflate_fast (deflate_state *s, int flush); + #ifndef FASTEST +! local block_state deflate_slow (deflate_state *s, int flush); + #endif +! local block_state deflate_rle (deflate_state *s, int flush); +! local block_state deflate_huff (deflate_state *s, int flush); +! local void lm_init (deflate_state *s); +! local void putShortMSB (deflate_state *s, uInt b); +! local void flush_pending (z_streamp strm); +! local unsigned read_buf (z_streamp strm, Bytef *buf, unsigned size); + #ifdef ASMV + # pragma message("Assembler code may have bugs -- use at your own risk") +! void match_init (void); /* asm code initialization */ +! uInt longest_match (deflate_state *s, IPos cur_match); + #else +! local uInt longest_match (deflate_state *s, IPos cur_match); + #endif + + #ifdef ZLIB_DEBUG +! local void check_match (deflate_state *s, IPos start, IPos match, +! int length); + #endif + + /* =========================================================================== +diff -c zlib-1.2.11/deflate.h zlib-1.2.11-patched/deflate.h +*** zlib-1.2.11/deflate.h 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/deflate.h 2019-03-08 15:19:23.086547910 +0100 +*************** +*** 296,309 **** + memory checker errors from longest match routines */ + + /* in trees.c */ +! void ZLIB_INTERNAL _tr_init OF((deflate_state *s)); +! int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +! void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf, +! ulg stored_len, int last)); +! void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s)); +! void ZLIB_INTERNAL _tr_align OF((deflate_state *s)); +! void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, +! ulg stored_len, int last)); + + #define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +--- 296,309 ---- + memory checker errors from longest match routines */ + + /* in trees.c */ +! void ZLIB_INTERNAL _tr_init (deflate_state *s); +! int ZLIB_INTERNAL _tr_tally (deflate_state *s, unsigned dist, unsigned lc); +! void ZLIB_INTERNAL _tr_flush_block (deflate_state *s, charf *buf, +! ulg stored_len, int last); +! void ZLIB_INTERNAL _tr_flush_bits (deflate_state *s); +! void ZLIB_INTERNAL _tr_align (deflate_state *s); +! void ZLIB_INTERNAL _tr_stored_block (deflate_state *s, charf *buf, +! ulg stored_len, int last); + + #define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +Common subdirectories: zlib-1.2.11/doc and zlib-1.2.11-patched/doc +Common subdirectories: zlib-1.2.11/examples and zlib-1.2.11-patched/examples +diff -c zlib-1.2.11/gzguts.h zlib-1.2.11-patched/gzguts.h +*** zlib-1.2.11/gzguts.h 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/gzguts.h 2019-03-08 15:19:23.070548070 +0100 +*************** +*** 119,126 **** + + /* gz* functions always use library allocation functions */ + #ifndef STDC +! extern voidp malloc OF((uInt size)); +! extern void free OF((voidpf ptr)); + #endif + + /* get errno and strerror definition */ +--- 119,126 ---- + + /* gz* functions always use library allocation functions */ + #ifndef STDC +! extern voidp malloc (uInt size); +! extern void free (voidpf ptr); + #endif + + /* get errno and strerror definition */ +*************** +*** 138,147 **** + + /* provide prototypes for these when building zlib without LFS */ + #if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0 +! ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); +! ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); +! ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); +! ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); + #endif + + /* default memLevel */ +--- 138,147 ---- + + /* provide prototypes for these when building zlib without LFS */ + #if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0 +! ZEXTERN gzFile ZEXPORT gzopen64 (const char *, const char *); +! ZEXTERN z_off64_t ZEXPORT gzseek64 (gzFile, z_off64_t, int); +! ZEXTERN z_off64_t ZEXPORT gztell64 (gzFile); +! ZEXTERN z_off64_t ZEXPORT gzoffset64 (gzFile); + #endif + + /* default memLevel */ +*************** +*** 202,210 **** + typedef gz_state FAR *gz_statep; + + /* shared functions */ +! void ZLIB_INTERNAL gz_error OF((gz_statep, int, const char *)); + #if defined UNDER_CE +! char ZLIB_INTERNAL *gz_strwinerror OF((DWORD error)); + #endif + + /* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t +--- 202,210 ---- + typedef gz_state FAR *gz_statep; + + /* shared functions */ +! void ZLIB_INTERNAL gz_error (gz_statep, int, const char *); + #if defined UNDER_CE +! char ZLIB_INTERNAL *gz_strwinerror (DWORD error); + #endif + + /* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t +*************** +*** 213,218 **** + #ifdef INT_MAX + # define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX) + #else +! unsigned ZLIB_INTERNAL gz_intmax OF((void)); + # define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) + #endif +--- 213,218 ---- + #ifdef INT_MAX + # define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX) + #else +! unsigned ZLIB_INTERNAL gz_intmax (void); + # define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) + #endif +diff -c zlib-1.2.11/gzlib.c zlib-1.2.11-patched/gzlib.c +*** zlib-1.2.11/gzlib.c 2017-01-15 18:29:40.000000000 +0100 +--- zlib-1.2.11-patched/gzlib.c 2019-03-08 15:19:23.378545000 +0100 +*************** +*** 16,23 **** + #endif + + /* Local functions */ +! local void gz_reset OF((gz_statep)); +! local gzFile gz_open OF((const void *, int, const char *)); + + #if defined UNDER_CE + +--- 16,23 ---- + #endif + + /* Local functions */ +! local void gz_reset (gz_statep); +! local gzFile gz_open (const void *, int, const char *); + + #if defined UNDER_CE + +diff -c zlib-1.2.11/gzread.c zlib-1.2.11-patched/gzread.c +*** zlib-1.2.11/gzread.c 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/gzread.c 2019-03-08 15:19:23.370545079 +0100 +*************** +*** 6,18 **** + #include "gzguts.h" + + /* Local functions */ +! local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *)); +! local int gz_avail OF((gz_statep)); +! local int gz_look OF((gz_statep)); +! local int gz_decomp OF((gz_statep)); +! local int gz_fetch OF((gz_statep)); +! local int gz_skip OF((gz_statep, z_off64_t)); +! local z_size_t gz_read OF((gz_statep, voidp, z_size_t)); + + /* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from + state->fd, and update state->eof, state->err, and state->msg as appropriate. +--- 6,18 ---- + #include "gzguts.h" + + /* Local functions */ +! local int gz_load (gz_statep, unsigned char *, unsigned, unsigned *); +! local int gz_avail (gz_statep); +! local int gz_look (gz_statep); +! local int gz_decomp (gz_statep); +! local int gz_fetch (gz_statep); +! local int gz_skip (gz_statep, z_off64_t); +! local z_size_t gz_read (gz_statep, voidp, z_size_t); + + /* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from + state->fd, and update state->eof, state->err, and state->msg as appropriate. +diff -c zlib-1.2.11/gzwrite.c zlib-1.2.11-patched/gzwrite.c +*** zlib-1.2.11/gzwrite.c 2017-01-15 18:29:40.000000000 +0100 +--- zlib-1.2.11-patched/gzwrite.c 2019-03-08 15:19:23.434544440 +0100 +*************** +*** 6,15 **** + #include "gzguts.h" + + /* Local functions */ +! local int gz_init OF((gz_statep)); +! local int gz_comp OF((gz_statep, int)); +! local int gz_zero OF((gz_statep, z_off64_t)); +! local z_size_t gz_write OF((gz_statep, voidpc, z_size_t)); + + /* Initialize state for writing a gzip file. Mark initialization by setting + state->size to non-zero. Return -1 on a memory allocation failure, or 0 on +--- 6,15 ---- + #include "gzguts.h" + + /* Local functions */ +! local int gz_init (gz_statep); +! local int gz_comp (gz_statep, int); +! local int gz_zero (gz_statep, z_off64_t); +! local z_size_t gz_write (gz_statep, voidpc, z_size_t); + + /* Initialize state for writing a gzip file. Mark initialization by setting + state->size to non-zero. Return -1 on a memory allocation failure, or 0 on +diff -c zlib-1.2.11/infback.c zlib-1.2.11-patched/infback.c +*** zlib-1.2.11/infback.c 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/infback.c 2019-03-08 15:19:23.458544201 +0100 +*************** +*** 16,22 **** + #include "inffast.h" + + /* function prototypes */ +! local void fixedtables OF((struct inflate_state FAR *state)); + + /* + strm provides memory allocation functions in zalloc and zfree, or +--- 16,22 ---- + #include "inffast.h" + + /* function prototypes */ +! local void fixedtables (struct inflate_state FAR *state); + + /* + strm provides memory allocation functions in zalloc and zfree, or +diff -c zlib-1.2.11/inffast.h zlib-1.2.11-patched/inffast.h +*** zlib-1.2.11/inffast.h 2010-04-19 06:16:01.000000000 +0200 +--- zlib-1.2.11-patched/inffast.h 2019-03-08 15:19:23.070548070 +0100 +*************** +*** 8,11 **** + subject to change. Applications should only use zlib.h. + */ + +! void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start)); +--- 8,11 ---- + subject to change. Applications should only use zlib.h. + */ + +! void ZLIB_INTERNAL inflate_fast (z_streamp strm, unsigned start); +diff -c zlib-1.2.11/inflate.c zlib-1.2.11-patched/inflate.c +*** zlib-1.2.11/inflate.c 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/inflate.c 2019-03-08 15:19:23.446544320 +0100 +*************** +*** 92,106 **** + #endif + + /* function prototypes */ +! local int inflateStateCheck OF((z_streamp strm)); +! local void fixedtables OF((struct inflate_state FAR *state)); +! local int updatewindow OF((z_streamp strm, const unsigned char FAR *end, +! unsigned copy)); + #ifdef BUILDFIXED +! void makefixed OF((void)); + #endif +! local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf, +! unsigned len)); + + local int inflateStateCheck(strm) + z_streamp strm; +--- 92,106 ---- + #endif + + /* function prototypes */ +! local int inflateStateCheck (z_streamp strm); +! local void fixedtables (struct inflate_state FAR *state); +! local int updatewindow (z_streamp strm, const unsigned char FAR *end, +! unsigned copy); + #ifdef BUILDFIXED +! void makefixed (void); + #endif +! local unsigned syncsearch (unsigned FAR *have, const unsigned char FAR *buf, +! unsigned len); + + local int inflateStateCheck(strm) + z_streamp strm; +diff -c zlib-1.2.11/inftrees.h zlib-1.2.11-patched/inftrees.h +*** zlib-1.2.11/inftrees.h 2010-04-19 06:15:26.000000000 +0200 +--- zlib-1.2.11-patched/inftrees.h 2019-03-08 15:19:23.450544283 +0100 +*************** +*** 57,62 **** + DISTS + } codetype; + +! int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, +! unsigned FAR *bits, unsigned short FAR *work)); +--- 57,62 ---- + DISTS + } codetype; + +! int ZLIB_INTERNAL inflate_table (codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, +! unsigned FAR *bits, unsigned short FAR *work); +Common subdirectories: zlib-1.2.11/msdos and zlib-1.2.11-patched/msdos +Common subdirectories: zlib-1.2.11/nintendods and zlib-1.2.11-patched/nintendods +Common subdirectories: zlib-1.2.11/old and zlib-1.2.11-patched/old +Common subdirectories: zlib-1.2.11/os400 and zlib-1.2.11-patched/os400 +Common subdirectories: zlib-1.2.11/qnx and zlib-1.2.11-patched/qnx +Common subdirectories: zlib-1.2.11/test and zlib-1.2.11-patched/test +diff -c zlib-1.2.11/trees.c zlib-1.2.11-patched/trees.c +*** zlib-1.2.11/trees.c 2017-01-15 18:07:14.000000000 +0100 +--- zlib-1.2.11-patched/trees.c 2019-03-08 15:19:23.446544320 +0100 +*************** +*** 135,160 **** + * Local (static) routines in this file. + */ + +! local void tr_static_init OF((void)); +! local void init_block OF((deflate_state *s)); +! local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); +! local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); +! local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); +! local void build_tree OF((deflate_state *s, tree_desc *desc)); +! local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); +! local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); +! local int build_bl_tree OF((deflate_state *s)); +! local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, +! int blcodes)); +! local void compress_block OF((deflate_state *s, const ct_data *ltree, +! const ct_data *dtree)); +! local int detect_data_type OF((deflate_state *s)); +! local unsigned bi_reverse OF((unsigned value, int length)); +! local void bi_windup OF((deflate_state *s)); +! local void bi_flush OF((deflate_state *s)); + + #ifdef GEN_TREES_H +! local void gen_trees_header OF((void)); + #endif + + #ifndef ZLIB_DEBUG +--- 135,160 ---- + * Local (static) routines in this file. + */ + +! local void tr_static_init (void); +! local void init_block (deflate_state *s); +! local void pqdownheap (deflate_state *s, ct_data *tree, int k); +! local void gen_bitlen (deflate_state *s, tree_desc *desc); +! local void gen_codes (ct_data *tree, int max_code, ushf *bl_count); +! local void build_tree (deflate_state *s, tree_desc *desc); +! local void scan_tree (deflate_state *s, ct_data *tree, int max_code); +! local void send_tree (deflate_state *s, ct_data *tree, int max_code); +! local int build_bl_tree (deflate_state *s); +! local void send_all_trees (deflate_state *s, int lcodes, int dcodes, +! int blcodes); +! local void compress_block (deflate_state *s, const ct_data *ltree, +! const ct_data *dtree); +! local int detect_data_type (deflate_state *s); +! local unsigned bi_reverse (unsigned value, int length); +! local void bi_windup (deflate_state *s); +! local void bi_flush (deflate_state *s); + + #ifdef GEN_TREES_H +! local void gen_trees_header (void); + #endif + + #ifndef ZLIB_DEBUG +*************** +*** 181,187 **** + * IN assertion: length <= 16 and value fits in length bits. + */ + #ifdef ZLIB_DEBUG +! local void send_bits OF((deflate_state *s, int value, int length)); + + local void send_bits(s, value, length) + deflate_state *s; +--- 181,187 ---- + * IN assertion: length <= 16 and value fits in length bits. + */ + #ifdef ZLIB_DEBUG +! local void send_bits (deflate_state *s, int value, int length); + + local void send_bits(s, value, length) + deflate_state *s; +Common subdirectories: zlib-1.2.11/watcom and zlib-1.2.11-patched/watcom +Common subdirectories: zlib-1.2.11/win32 and zlib-1.2.11-patched/win32 +diff -c zlib-1.2.11/zconf.h zlib-1.2.11-patched/zconf.h +*** zlib-1.2.11/zconf.h 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/zconf.h 2019-03-08 15:19:23.454544243 +0100 +*************** +*** 287,295 **** + + #ifndef OF /* function prototypes */ + # ifdef STDC +! # define OF(args) args + # else +! # define OF(args) () + # endif + #endif + +--- 287,295 ---- + + #ifndef OF /* function prototypes */ + # ifdef STDC +! # define args args + # else +! # define args () + # endif + #endif + +diff -c zlib-1.2.11/zconf.h.cmakein zlib-1.2.11-patched/zconf.h.cmakein +*** zlib-1.2.11/zconf.h.cmakein 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/zconf.h.cmakein 2019-03-08 15:19:23.458544201 +0100 +*************** +*** 289,297 **** + + #ifndef OF /* function prototypes */ + # ifdef STDC +! # define OF(args) args + # else +! # define OF(args) () + # endif + #endif + +--- 289,297 ---- + + #ifndef OF /* function prototypes */ + # ifdef STDC +! # define args args + # else +! # define args () + # endif + #endif + +diff -c zlib-1.2.11/zconf.h.in zlib-1.2.11-patched/zconf.h.in +*** zlib-1.2.11/zconf.h.in 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/zconf.h.in 2019-03-08 15:19:23.418544601 +0100 +*************** +*** 287,295 **** + + #ifndef OF /* function prototypes */ + # ifdef STDC +! # define OF(args) args + # else +! # define OF(args) () + # endif + #endif + +--- 287,295 ---- + + #ifndef OF /* function prototypes */ + # ifdef STDC +! # define args args + # else +! # define args () + # endif + #endif + +diff -c zlib-1.2.11/zlib.h zlib-1.2.11-patched/zlib.h +*** zlib-1.2.11/zlib.h 2017-01-15 18:29:40.000000000 +0100 +--- zlib-1.2.11-patched/zlib.h 2019-03-08 15:19:23.406544721 +0100 +*************** +*** 78,85 **** + even in the case of corrupted input. + */ + +! typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +! typedef void (*free_func) OF((voidpf opaque, voidpf address)); + + struct internal_state; + +--- 78,85 ---- + even in the case of corrupted input. + */ + +! typedef voidpf (*alloc_func) (voidpf opaque, uInt items, uInt size); +! typedef void (*free_func) (voidpf opaque, voidpf address); + + struct internal_state; + +*************** +*** 217,223 **** + + /* basic functions */ + +! ZEXTERN const char * ZEXPORT zlibVersion OF((void)); + /* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is not + compatible with the zlib.h header file used by the application. This check +--- 217,223 ---- + + /* basic functions */ + +! ZEXTERN const char * ZEXPORT zlibVersion (void); + /* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is not + compatible with the zlib.h header file used by the application. This check +*************** +*** 225,231 **** + */ + + /* +! ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. If +--- 225,231 ---- + */ + + /* +! ZEXTERN int ZEXPORT deflateInit (z_streamp strm, int level); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. If +*************** +*** 247,253 **** + */ + + +! ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); + /* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce +--- 247,253 ---- + */ + + +! ZEXTERN int ZEXPORT deflate (z_streamp strm, int flush); + /* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce +*************** +*** 360,366 **** + */ + + +! ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); + /* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending +--- 360,366 ---- + */ + + +! ZEXTERN int ZEXPORT deflateEnd (z_streamp strm); + /* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending +*************** +*** 375,381 **** + + + /* +! ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by +--- 375,381 ---- + + + /* +! ZEXTERN int ZEXPORT inflateInit (z_streamp strm); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by +*************** +*** 397,403 **** + */ + + +! ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); + /* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce +--- 397,403 ---- + */ + + +! ZEXTERN int ZEXPORT inflate (z_streamp strm, int flush); + /* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce +*************** +*** 517,523 **** + */ + + +! ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); + /* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending +--- 517,523 ---- + */ + + +! ZEXTERN int ZEXPORT inflateEnd (z_streamp strm); + /* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending +*************** +*** 535,546 **** + */ + + /* +! ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, +! int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by the +--- 535,546 ---- + */ + + /* +! ZEXTERN int ZEXPORT deflateInit2 (z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, +! int strategy); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by the +*************** +*** 608,616 **** + compression: this will be done by deflate(). + */ + +! ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, +! uInt dictLength)); + /* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. When using the zlib format, this +--- 608,616 ---- + compression: this will be done by deflate(). + */ + +! ZEXTERN int ZEXPORT deflateSetDictionary (z_streamp strm, + const Bytef *dictionary, +! uInt dictLength); + /* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. When using the zlib format, this +*************** +*** 652,660 **** + not perform any compression: this will be done by deflate(). + */ + +! ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm, + Bytef *dictionary, +! uInt *dictLength)); + /* + Returns the sliding dictionary being maintained by deflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied +--- 652,660 ---- + not perform any compression: this will be done by deflate(). + */ + +! ZEXTERN int ZEXPORT deflateGetDictionary (z_streamp strm, + Bytef *dictionary, +! uInt *dictLength); + /* + Returns the sliding dictionary being maintained by deflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied +*************** +*** 674,681 **** + stream state is inconsistent. + */ + +! ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, +! z_streamp source)); + /* + Sets the destination stream as a complete copy of the source stream. + +--- 674,681 ---- + stream state is inconsistent. + */ + +! ZEXTERN int ZEXPORT deflateCopy (z_streamp dest, +! z_streamp source); + /* + Sets the destination stream as a complete copy of the source stream. + +*************** +*** 692,698 **** + destination. + */ + +! ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); + /* + This function is equivalent to deflateEnd followed by deflateInit, but + does not free and reallocate the internal compression state. The stream +--- 692,698 ---- + destination. + */ + +! ZEXTERN int ZEXPORT deflateReset (z_streamp strm); + /* + This function is equivalent to deflateEnd followed by deflateInit, but + does not free and reallocate the internal compression state. The stream +*************** +*** 703,711 **** + stream state was inconsistent (such as zalloc or state being Z_NULL). + */ + +! ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, +! int strategy)); + /* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2(). This can be +--- 703,711 ---- + stream state was inconsistent (such as zalloc or state being Z_NULL). + */ + +! ZEXTERN int ZEXPORT deflateParams (z_streamp strm, + int level, +! int strategy); + /* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2(). This can be +*************** +*** 740,750 **** + retried with more output space. + */ + +! ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, +! int max_chain)); + /* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for +--- 740,750 ---- + retried with more output space. + */ + +! ZEXTERN int ZEXPORT deflateTune (z_streamp strm, + int good_length, + int max_lazy, + int nice_length, +! int max_chain); + /* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for +*************** +*** 757,764 **** + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +! ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, +! uLong sourceLen)); + /* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() or +--- 757,764 ---- + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +! ZEXTERN uLong ZEXPORT deflateBound (z_streamp strm, +! uLong sourceLen); + /* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() or +*************** +*** 772,780 **** + than Z_FINISH or Z_NO_FLUSH are used. + */ + +! ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, + unsigned *pending, +! int *bits)); + /* + deflatePending() returns the number of bytes and bits of output that have + been generated, but not yet provided in the available output. The bytes not +--- 772,780 ---- + than Z_FINISH or Z_NO_FLUSH are used. + */ + +! ZEXTERN int ZEXPORT deflatePending (z_streamp strm, + unsigned *pending, +! int *bits); + /* + deflatePending() returns the number of bytes and bits of output that have + been generated, but not yet provided in the available output. The bytes not +*************** +*** 787,795 **** + stream state was inconsistent. + */ + +! ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, +! int value)); + /* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the bits +--- 787,795 ---- + stream state was inconsistent. + */ + +! ZEXTERN int ZEXPORT deflatePrime (z_streamp strm, + int bits, +! int value); + /* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the bits +*************** +*** 804,811 **** + source stream state was inconsistent. + */ + +! ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, +! gz_headerp head)); + /* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called +--- 804,811 ---- + source stream state was inconsistent. + */ + +! ZEXTERN int ZEXPORT deflateSetHeader (z_streamp strm, +! gz_headerp head); + /* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called +*************** +*** 829,836 **** + */ + + /* +! ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, +! int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized +--- 829,836 ---- + */ + + /* +! ZEXTERN int ZEXPORT inflateInit2 (z_streamp strm, +! int windowBits); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized +*************** +*** 881,889 **** + deferred until inflate() is called. + */ + +! ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, +! uInt dictLength)); + /* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, +--- 881,889 ---- + deferred until inflate() is called. + */ + +! ZEXTERN int ZEXPORT inflateSetDictionary (z_streamp strm, + const Bytef *dictionary, +! uInt dictLength); + /* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, +*************** +*** 904,912 **** + inflate(). + */ + +! ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, + Bytef *dictionary, +! uInt *dictLength)); + /* + Returns the sliding dictionary being maintained by inflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied +--- 904,912 ---- + inflate(). + */ + +! ZEXTERN int ZEXPORT inflateGetDictionary (z_streamp strm, + Bytef *dictionary, +! uInt *dictLength); + /* + Returns the sliding dictionary being maintained by inflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied +*************** +*** 919,925 **** + stream state is inconsistent. + */ + +! ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); + /* + Skips invalid compressed data until a possible full flush point (see above + for the description of deflate with Z_FULL_FLUSH) can be found, or until all +--- 919,925 ---- + stream state is inconsistent. + */ + +! ZEXTERN int ZEXPORT inflateSync (z_streamp strm); + /* + Skips invalid compressed data until a possible full flush point (see above + for the description of deflate with Z_FULL_FLUSH) can be found, or until all +*************** +*** 938,945 **** + input each time, until success or end of the input data. + */ + +! ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, +! z_streamp source)); + /* + Sets the destination stream as a complete copy of the source stream. + +--- 938,945 ---- + input each time, until success or end of the input data. + */ + +! ZEXTERN int ZEXPORT inflateCopy (z_streamp dest, +! z_streamp source); + /* + Sets the destination stream as a complete copy of the source stream. + +*************** +*** 954,960 **** + destination. + */ + +! ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); + /* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate the internal decompression state. The +--- 954,960 ---- + destination. + */ + +! ZEXTERN int ZEXPORT inflateReset (z_streamp strm); + /* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate the internal decompression state. The +*************** +*** 964,971 **** + stream state was inconsistent (such as zalloc or state being Z_NULL). + */ + +! ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm, +! int windowBits)); + /* + This function is the same as inflateReset, but it also permits changing + the wrap and window size requests. The windowBits parameter is interpreted +--- 964,971 ---- + stream state was inconsistent (such as zalloc or state being Z_NULL). + */ + +! ZEXTERN int ZEXPORT inflateReset2 (z_streamp strm, +! int windowBits); + /* + This function is the same as inflateReset, but it also permits changing + the wrap and window size requests. The windowBits parameter is interpreted +*************** +*** 978,986 **** + the windowBits parameter is invalid. + */ + +! ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, +! int value)); + /* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the +--- 978,986 ---- + the windowBits parameter is invalid. + */ + +! ZEXTERN int ZEXPORT inflatePrime (z_streamp strm, + int bits, +! int value); + /* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the +*************** +*** 999,1005 **** + stream state was inconsistent. + */ + +! ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm)); + /* + This function returns two values, one in the lower 16 bits of the return + value, and the other in the remaining upper bits, obtained by shifting the +--- 999,1005 ---- + stream state was inconsistent. + */ + +! ZEXTERN long ZEXPORT inflateMark (z_streamp strm); + /* + This function returns two values, one in the lower 16 bits of the return + value, and the other in the remaining upper bits, obtained by shifting the +*************** +*** 1027,1034 **** + source stream state was inconsistent. + */ + +! ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, +! gz_headerp head)); + /* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after +--- 1027,1034 ---- + source stream state was inconsistent. + */ + +! ZEXTERN int ZEXPORT inflateGetHeader (z_streamp strm, +! gz_headerp head); + /* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after +*************** +*** 1068,1075 **** + */ + + /* +! ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, +! unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized +--- 1068,1075 ---- + */ + + /* +! ZEXTERN int ZEXPORT inflateBackInit (z_streamp strm, int windowBits, +! unsigned char FAR *window); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized +*************** +*** 1089,1101 **** + the version of the header file. + */ + +! typedef unsigned (*in_func) OF((void FAR *, +! z_const unsigned char FAR * FAR *)); +! typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +! ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, +! out_func out, void FAR *out_desc)); + /* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is potentially more efficient than +--- 1089,1101 ---- + the version of the header file. + */ + +! typedef unsigned (*in_func) (void FAR *, +! z_const unsigned char FAR * FAR *); +! typedef int (*out_func) (void FAR *, unsigned char FAR *, unsigned); + +! ZEXTERN int ZEXPORT inflateBack (z_streamp strm, + in_func in, void FAR *in_desc, +! out_func out, void FAR *out_desc); + /* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is potentially more efficient than +*************** +*** 1163,1169 **** + cannot return Z_OK. + */ + +! ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); + /* + All memory allocated by inflateBackInit() is freed. + +--- 1163,1169 ---- + cannot return Z_OK. + */ + +! ZEXTERN int ZEXPORT inflateBackEnd (z_streamp strm); + /* + All memory allocated by inflateBackInit() is freed. + +*************** +*** 1171,1177 **** + state was inconsistent. + */ + +! ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); + /* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: +--- 1171,1177 ---- + state was inconsistent. + */ + +! ZEXTERN uLong ZEXPORT zlibCompileFlags (void); + /* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: +*************** +*** 1224,1231 **** + you need special options. + */ + +! ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, +! const Bytef *source, uLong sourceLen)); + /* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size +--- 1224,1231 ---- + you need special options. + */ + +! ZEXTERN int ZEXPORT compress (Bytef *dest, uLongf *destLen, +! const Bytef *source, uLong sourceLen); + /* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size +*************** +*** 1239,1247 **** + buffer. + */ + +! ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, +! int level)); + /* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte +--- 1239,1247 ---- + buffer. + */ + +! ZEXTERN int ZEXPORT compress2 (Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, +! int level); + /* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte +*************** +*** 1255,1269 **** + Z_STREAM_ERROR if the level parameter is invalid. + */ + +! ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); + /* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before a + compress() or compress2() call to allocate the destination buffer. + */ + +! ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, +! const Bytef *source, uLong sourceLen)); + /* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size +--- 1255,1269 ---- + Z_STREAM_ERROR if the level parameter is invalid. + */ + +! ZEXTERN uLong ZEXPORT compressBound (uLong sourceLen); + /* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before a + compress() or compress2() call to allocate the destination buffer. + */ + +! ZEXTERN int ZEXPORT uncompress (Bytef *dest, uLongf *destLen, +! const Bytef *source, uLong sourceLen); + /* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size +*************** +*** 1280,1287 **** + buffer with the uncompressed data up to that point. + */ + +! ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen, +! const Bytef *source, uLong *sourceLen)); + /* + Same as uncompress, except that sourceLen is a pointer, where the + length of the source is *sourceLen. On return, *sourceLen is the number of +--- 1280,1287 ---- + buffer with the uncompressed data up to that point. + */ + +! ZEXTERN int ZEXPORT uncompress2 (Bytef *dest, uLongf *destLen, +! const Bytef *source, uLong *sourceLen); + /* + Same as uncompress, except that sourceLen is a pointer, where the + length of the source is *sourceLen. On return, *sourceLen is the number of +*************** +*** 1300,1306 **** + typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ + + /* +! ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); + + Opens a gzip (.gz) file for reading or writing. The mode parameter is as + in fopen ("rb" or "wb") but can also include a compression level ("wb9") or +--- 1300,1306 ---- + typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ + + /* +! ZEXTERN gzFile ZEXPORT gzopen (const char *path, const char *mode); + + Opens a gzip (.gz) file for reading or writing. The mode parameter is as + in fopen ("rb" or "wb") but can also include a compression level ("wb9") or +*************** +*** 1337,1343 **** + file could not be opened. + */ + +! ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); + /* + gzdopen associates a gzFile with the file descriptor fd. File descriptors + are obtained from calls like open, dup, creat, pipe or fileno (if the file +--- 1337,1343 ---- + file could not be opened. + */ + +! ZEXTERN gzFile ZEXPORT gzdopen (int fd, const char *mode); + /* + gzdopen associates a gzFile with the file descriptor fd. File descriptors + are obtained from calls like open, dup, creat, pipe or fileno (if the file +*************** +*** 1360,1366 **** + will not detect if fd is invalid (unless fd is -1). + */ + +! ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size)); + /* + Set the internal buffer size used by this library's functions. The + default buffer size is 8192 bytes. This function must be called after +--- 1360,1366 ---- + will not detect if fd is invalid (unless fd is -1). + */ + +! ZEXTERN int ZEXPORT gzbuffer (gzFile file, unsigned size); + /* + Set the internal buffer size used by this library's functions. The + default buffer size is 8192 bytes. This function must be called after +*************** +*** 1376,1382 **** + too late. + */ + +! ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); + /* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. Previously provided +--- 1376,1382 ---- + too late. + */ + +! ZEXTERN int ZEXPORT gzsetparams (gzFile file, int level, int strategy); + /* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. Previously provided +*************** +*** 1387,1393 **** + or Z_MEM_ERROR if there is a memory allocation error. + */ + +! ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); + /* + Reads the given number of uncompressed bytes from the compressed file. If + the input file is not in gzip format, gzread copies the given number of +--- 1387,1393 ---- + or Z_MEM_ERROR if there is a memory allocation error. + */ + +! ZEXTERN int ZEXPORT gzread (gzFile file, voidp buf, unsigned len); + /* + Reads the given number of uncompressed bytes from the compressed file. If + the input file is not in gzip format, gzread copies the given number of +*************** +*** 1417,1424 **** + Z_STREAM_ERROR. + */ + +! ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems, +! gzFile file)); + /* + Read up to nitems items of size size from file to buf, otherwise operating + as gzread() does. This duplicates the interface of stdio's fread(), with +--- 1417,1424 ---- + Z_STREAM_ERROR. + */ + +! ZEXTERN z_size_t ZEXPORT gzfread (voidp buf, z_size_t size, z_size_t nitems, +! gzFile file); + /* + Read up to nitems items of size size from file to buf, otherwise operating + as gzread() does. This duplicates the interface of stdio's fread(), with +*************** +*** 1443,1458 **** + file, reseting and retrying on end-of-file, when size is not 1. + */ + +! ZEXTERN int ZEXPORT gzwrite OF((gzFile file, +! voidpc buf, unsigned len)); + /* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes written or 0 in case of + error. + */ + +! ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size, +! z_size_t nitems, gzFile file)); + /* + gzfwrite() writes nitems items of size size from buf to file, duplicating + the interface of stdio's fwrite(), with size_t request and return types. If +--- 1443,1458 ---- + file, reseting and retrying on end-of-file, when size is not 1. + */ + +! ZEXTERN int ZEXPORT gzwrite (gzFile file, +! voidpc buf, unsigned len); + /* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes written or 0 in case of + error. + */ + +! ZEXTERN z_size_t ZEXPORT gzfwrite (voidpc buf, z_size_t size, +! z_size_t nitems, gzFile file); + /* + gzfwrite() writes nitems items of size size from buf to file, duplicating + the interface of stdio's fwrite(), with size_t request and return types. If +*************** +*** 1480,1486 **** + This can be determined using zlibCompileFlags(). + */ + +! ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); + /* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. +--- 1480,1486 ---- + This can be determined using zlibCompileFlags(). + */ + +! ZEXTERN int ZEXPORT gzputs (gzFile file, const char *s); + /* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. +*************** +*** 1488,1494 **** + gzputs returns the number of characters written, or -1 in case of error. + */ + +! ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); + /* + Reads bytes from the compressed file until len-1 characters are read, or a + newline character is read and transferred to buf, or an end-of-file +--- 1488,1494 ---- + gzputs returns the number of characters written, or -1 in case of error. + */ + +! ZEXTERN char * ZEXPORT gzgets (gzFile file, char *buf, int len); + /* + Reads bytes from the compressed file until len-1 characters are read, or a + newline character is read and transferred to buf, or an end-of-file +*************** +*** 1501,1513 **** + buf are indeterminate. + */ + +! ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); + /* + Writes c, converted to an unsigned char, into the compressed file. gzputc + returns the value that was written, or -1 in case of error. + */ + +! ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); + /* + Reads one byte from the compressed file. gzgetc returns this byte or -1 + in case of end of file or error. This is implemented as a macro for speed. +--- 1501,1513 ---- + buf are indeterminate. + */ + +! ZEXTERN int ZEXPORT gzputc (gzFile file, int c); + /* + Writes c, converted to an unsigned char, into the compressed file. gzputc + returns the value that was written, or -1 in case of error. + */ + +! ZEXTERN int ZEXPORT gzgetc (gzFile file); + /* + Reads one byte from the compressed file. gzgetc returns this byte or -1 + in case of end of file or error. This is implemented as a macro for speed. +*************** +*** 1516,1522 **** + points to has been clobbered or not. + */ + +! ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); + /* + Push one character back onto the stream to be read as the first character + on the next read. At least one character of push-back is allowed. +--- 1516,1522 ---- + points to has been clobbered or not. + */ + +! ZEXTERN int ZEXPORT gzungetc (int c, gzFile file); + /* + Push one character back onto the stream to be read as the first character + on the next read. At least one character of push-back is allowed. +*************** +*** 1528,1534 **** + gzseek() or gzrewind(). + */ + +! ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); + /* + Flushes all pending output into the compressed file. The parameter flush + is as in the deflate() function. The return value is the zlib error number +--- 1528,1534 ---- + gzseek() or gzrewind(). + */ + +! ZEXTERN int ZEXPORT gzflush (gzFile file, int flush); + /* + Flushes all pending output into the compressed file. The parameter flush + is as in the deflate() function. The return value is the zlib error number +*************** +*** 1544,1551 **** + */ + + /* +! ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, +! z_off_t offset, int whence)); + + Sets the starting position for the next gzread or gzwrite on the given + compressed file. The offset represents a number of bytes in the +--- 1544,1551 ---- + */ + + /* +! ZEXTERN z_off_t ZEXPORT gzseek (gzFile file, +! z_off_t offset, int whence); + + Sets the starting position for the next gzread or gzwrite on the given + compressed file. The offset represents a number of bytes in the +*************** +*** 1563,1569 **** + would be before the current position. + */ + +! ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); + /* + Rewinds the given file. This function is supported only for reading. + +--- 1563,1569 ---- + would be before the current position. + */ + +! ZEXTERN int ZEXPORT gzrewind (gzFile file); + /* + Rewinds the given file. This function is supported only for reading. + +*************** +*** 1571,1577 **** + */ + + /* +! ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); + + Returns the starting position for the next gzread or gzwrite on the given + compressed file. This position represents a number of bytes in the +--- 1571,1577 ---- + */ + + /* +! ZEXTERN z_off_t ZEXPORT gztell (gzFile file); + + Returns the starting position for the next gzread or gzwrite on the given + compressed file. This position represents a number of bytes in the +*************** +*** 1582,1588 **** + */ + + /* +! ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file)); + + Returns the current offset in the file being read or written. This offset + includes the count of bytes that precede the gzip stream, for example when +--- 1582,1588 ---- + */ + + /* +! ZEXTERN z_off_t ZEXPORT gzoffset (gzFile file); + + Returns the current offset in the file being read or written. This offset + includes the count of bytes that precede the gzip stream, for example when +*************** +*** 1591,1597 **** + for a progress indicator. On error, gzoffset() returns -1. + */ + +! ZEXTERN int ZEXPORT gzeof OF((gzFile file)); + /* + Returns true (1) if the end-of-file indicator has been set while reading, + false (0) otherwise. Note that the end-of-file indicator is set only if the +--- 1591,1597 ---- + for a progress indicator. On error, gzoffset() returns -1. + */ + +! ZEXTERN int ZEXPORT gzeof (gzFile file); + /* + Returns true (1) if the end-of-file indicator has been set while reading, + false (0) otherwise. Note that the end-of-file indicator is set only if the +*************** +*** 1606,1612 **** + has grown since the previous end of file was detected. + */ + +! ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); + /* + Returns true (1) if file is being copied directly while reading, or false + (0) if file is a gzip stream being decompressed. +--- 1606,1612 ---- + has grown since the previous end of file was detected. + */ + +! ZEXTERN int ZEXPORT gzdirect (gzFile file); + /* + Returns true (1) if file is being copied directly while reading, or false + (0) if file is a gzip stream being decompressed. +*************** +*** 1627,1633 **** + gzip file reading and decompression, which may not be desired.) + */ + +! ZEXTERN int ZEXPORT gzclose OF((gzFile file)); + /* + Flushes all pending output if necessary, closes the compressed file and + deallocates the (de)compression state. Note that once file is closed, you +--- 1627,1633 ---- + gzip file reading and decompression, which may not be desired.) + */ + +! ZEXTERN int ZEXPORT gzclose (gzFile file); + /* + Flushes all pending output if necessary, closes the compressed file and + deallocates the (de)compression state. Note that once file is closed, you +*************** +*** 1640,1647 **** + last read ended in the middle of a gzip stream, or Z_OK on success. + */ + +! ZEXTERN int ZEXPORT gzclose_r OF((gzFile file)); +! ZEXTERN int ZEXPORT gzclose_w OF((gzFile file)); + /* + Same as gzclose(), but gzclose_r() is only for use when reading, and + gzclose_w() is only for use when writing or appending. The advantage to +--- 1640,1647 ---- + last read ended in the middle of a gzip stream, or Z_OK on success. + */ + +! ZEXTERN int ZEXPORT gzclose_r (gzFile file); +! ZEXTERN int ZEXPORT gzclose_w (gzFile file); + /* + Same as gzclose(), but gzclose_r() is only for use when reading, and + gzclose_w() is only for use when writing or appending. The advantage to +*************** +*** 1652,1658 **** + zlib library. + */ + +! ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); + /* + Returns the error message for the last error which occurred on the given + compressed file. errnum is set to zlib error number. If an error occurred +--- 1652,1658 ---- + zlib library. + */ + +! ZEXTERN const char * ZEXPORT gzerror (gzFile file, int *errnum); + /* + Returns the error message for the last error which occurred on the given + compressed file. errnum is set to zlib error number. If an error occurred +*************** +*** 1668,1674 **** + functions above that do not distinguish those cases in their return values. + */ + +! ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); + /* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip +--- 1668,1674 ---- + functions above that do not distinguish those cases in their return values. + */ + +! ZEXTERN void ZEXPORT gzclearerr (gzFile file); + /* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip +*************** +*** 1685,1691 **** + library. + */ + +! ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); + /* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is Z_NULL, this function returns the +--- 1685,1691 ---- + library. + */ + +! ZEXTERN uLong ZEXPORT adler32 (uLong adler, const Bytef *buf, uInt len); + /* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is Z_NULL, this function returns the +*************** +*** 1704,1718 **** + if (adler != original_adler) error(); + */ + +! ZEXTERN uLong ZEXPORT adler32_z OF((uLong adler, const Bytef *buf, +! z_size_t len)); + /* + Same as adler32(), but with a size_t length. + */ + + /* +! ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, +! z_off_t len2)); + + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for +--- 1704,1718 ---- + if (adler != original_adler) error(); + */ + +! ZEXTERN uLong ZEXPORT adler32_z (uLong adler, const Bytef *buf, +! z_size_t len); + /* + Same as adler32(), but with a size_t length. + */ + + /* +! ZEXTERN uLong ZEXPORT adler32_combine (uLong adler1, uLong adler2, +! z_off_t len2); + + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for +*************** +*** 1722,1728 **** + negative, the result has no meaning or utility. + */ + +! ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); + /* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is Z_NULL, this function returns the required +--- 1722,1728 ---- + negative, the result has no meaning or utility. + */ + +! ZEXTERN uLong ZEXPORT crc32 (uLong crc, const Bytef *buf, uInt len); + /* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is Z_NULL, this function returns the required +*************** +*** 1739,1752 **** + if (crc != original_crc) error(); + */ + +! ZEXTERN uLong ZEXPORT crc32_z OF((uLong adler, const Bytef *buf, +! z_size_t len)); + /* + Same as crc32(), but with a size_t length. + */ + + /* +! ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were +--- 1739,1752 ---- + if (crc != original_crc) error(); + */ + +! ZEXTERN uLong ZEXPORT crc32_z (uLong adler, const Bytef *buf, +! z_size_t len); + /* + Same as crc32(), but with a size_t length. + */ + + /* +! ZEXTERN uLong ZEXPORT crc32_combine (uLong crc1, uLong crc2, z_off_t len2); + + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were +*************** +*** 1761,1780 **** + /* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +! ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, +! const char *version, int stream_size)); +! ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, +! const char *version, int stream_size)); +! ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, +! int stream_size)); +! ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, +! const char *version, int stream_size)); +! ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, +! int stream_size)); + #ifdef Z_PREFIX_SET + # define z_deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) +--- 1761,1780 ---- + /* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +! ZEXTERN int ZEXPORT deflateInit_ (z_streamp strm, int level, +! const char *version, int stream_size); +! ZEXTERN int ZEXPORT inflateInit_ (z_streamp strm, +! const char *version, int stream_size); +! ZEXTERN int ZEXPORT deflateInit2_ (z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, +! int stream_size); +! ZEXTERN int ZEXPORT inflateInit2_ (z_streamp strm, int windowBits, +! const char *version, int stream_size); +! ZEXTERN int ZEXPORT inflateBackInit_ (z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, +! int stream_size); + #ifdef Z_PREFIX_SET + # define z_deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) +*************** +*** 1819,1825 **** + unsigned char *next; + z_off64_t pos; + }; +! ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ + #ifdef Z_PREFIX_SET + # undef z_gzgetc + # define z_gzgetc(g) \ +--- 1819,1825 ---- + unsigned char *next; + z_off64_t pos; + }; +! ZEXTERN int ZEXPORT gzgetc_ (gzFile file); /* backward compatibility */ + #ifdef Z_PREFIX_SET + # undef z_gzgetc + # define z_gzgetc(g) \ +*************** +*** 1836,1847 **** + * without large file support, _LFS64_LARGEFILE must also be true + */ + #ifdef Z_LARGE64 +! ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); +! ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); +! ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); +! ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); +! ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t)); +! ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t)); + #endif + + #if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) +--- 1836,1847 ---- + * without large file support, _LFS64_LARGEFILE must also be true + */ + #ifdef Z_LARGE64 +! ZEXTERN gzFile ZEXPORT gzopen64 (const char *, const char *); +! ZEXTERN z_off64_t ZEXPORT gzseek64 (gzFile, z_off64_t, int); +! ZEXTERN z_off64_t ZEXPORT gztell64 (gzFile); +! ZEXTERN z_off64_t ZEXPORT gzoffset64 (gzFile); +! ZEXTERN uLong ZEXPORT adler32_combine64 (uLong, uLong, z_off64_t); +! ZEXTERN uLong ZEXPORT crc32_combine64 (uLong, uLong, z_off64_t); + #endif + + #if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) +*************** +*** 1861,1901 **** + # define crc32_combine crc32_combine64 + # endif + # ifndef Z_LARGE64 +! ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); +! ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int)); +! ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile)); +! ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile)); +! ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); +! ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); + # endif + #else +! ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *)); +! ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int)); +! ZEXTERN z_off_t ZEXPORT gztell OF((gzFile)); +! ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile)); +! ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); +! ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + #endif + + #else /* Z_SOLO */ + +! ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); +! ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + + #endif /* !Z_SOLO */ + + /* undocumented functions */ +! ZEXTERN const char * ZEXPORT zError OF((int)); +! ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); +! ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); +! ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); +! ZEXTERN int ZEXPORT inflateValidate OF((z_streamp, int)); +! ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF ((z_streamp)); +! ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); +! ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); + #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(Z_SOLO) +! ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path, +! const char *mode)); + #endif + #if defined(STDC) || defined(Z_HAVE_STDARG_H) + # ifndef Z_SOLO +--- 1861,1901 ---- + # define crc32_combine crc32_combine64 + # endif + # ifndef Z_LARGE64 +! ZEXTERN gzFile ZEXPORT gzopen64 (const char *, const char *); +! ZEXTERN z_off_t ZEXPORT gzseek64 (gzFile, z_off_t, int); +! ZEXTERN z_off_t ZEXPORT gztell64 (gzFile); +! ZEXTERN z_off_t ZEXPORT gzoffset64 (gzFile); +! ZEXTERN uLong ZEXPORT adler32_combine64 (uLong, uLong, z_off_t); +! ZEXTERN uLong ZEXPORT crc32_combine64 (uLong, uLong, z_off_t); + # endif + #else +! ZEXTERN gzFile ZEXPORT gzopen (const char *, const char *); +! ZEXTERN z_off_t ZEXPORT gzseek (gzFile, z_off_t, int); +! ZEXTERN z_off_t ZEXPORT gztell (gzFile); +! ZEXTERN z_off_t ZEXPORT gzoffset (gzFile); +! ZEXTERN uLong ZEXPORT adler32_combine (uLong, uLong, z_off_t); +! ZEXTERN uLong ZEXPORT crc32_combine (uLong, uLong, z_off_t); + #endif + + #else /* Z_SOLO */ + +! ZEXTERN uLong ZEXPORT adler32_combine (uLong, uLong, z_off_t); +! ZEXTERN uLong ZEXPORT crc32_combine (uLong, uLong, z_off_t); + + #endif /* !Z_SOLO */ + + /* undocumented functions */ +! ZEXTERN const char * ZEXPORT zError (int); +! ZEXTERN int ZEXPORT inflateSyncPoint (z_streamp); +! ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table (void); +! ZEXTERN int ZEXPORT inflateUndermine (z_streamp, int); +! ZEXTERN int ZEXPORT inflateValidate (z_streamp, int); +! ZEXTERN unsigned long ZEXPORT inflateCodesUsed (z_streamp); +! ZEXTERN int ZEXPORT inflateResetKeep (z_streamp); +! ZEXTERN int ZEXPORT deflateResetKeep (z_streamp); + #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(Z_SOLO) +! ZEXTERN gzFile ZEXPORT gzopen_w (const wchar_t *path, +! const char *mode); + #endif + #if defined(STDC) || defined(Z_HAVE_STDARG_H) + # ifndef Z_SOLO +diff -c zlib-1.2.11/zutil.c zlib-1.2.11-patched/zutil.c +*** zlib-1.2.11/zutil.c 2017-01-15 18:29:40.000000000 +0100 +--- zlib-1.2.11-patched/zutil.c 2019-03-08 15:19:23.438544402 +0100 +*************** +*** 297,305 **** + #ifndef MY_ZCALLOC /* Any system without a special alloc function */ + + #ifndef STDC +! extern voidp malloc OF((uInt size)); +! extern voidp calloc OF((uInt items, uInt size)); +! extern void free OF((voidpf ptr)); + #endif + + voidpf ZLIB_INTERNAL zcalloc (opaque, items, size) +--- 297,305 ---- + #ifndef MY_ZCALLOC /* Any system without a special alloc function */ + + #ifndef STDC +! extern voidp malloc (uInt size); +! extern voidp calloc (uInt items, uInt size); +! extern void free (voidpf ptr); + #endif + + voidpf ZLIB_INTERNAL zcalloc (opaque, items, size) +diff -c zlib-1.2.11/zutil.h zlib-1.2.11-patched/zutil.h +*** zlib-1.2.11/zutil.h 2017-01-01 08:37:10.000000000 +0100 +--- zlib-1.2.11-patched/zutil.h 2019-03-08 15:19:23.354545239 +0100 +*************** +*** 188,195 **** + /* provide prototypes for these when building zlib without LFS */ + #if !defined(_WIN32) && \ + (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) +! ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); +! ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); + #endif + + /* common defaults */ +--- 188,195 ---- + /* provide prototypes for these when building zlib without LFS */ + #if !defined(_WIN32) && \ + (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) +! ZEXTERN uLong ZEXPORT adler32_combine64 (uLong, uLong, z_off_t); +! ZEXTERN uLong ZEXPORT crc32_combine64 (uLong, uLong, z_off_t); + #endif + + /* common defaults */ +*************** +*** 228,243 **** + # define zmemzero(dest, len) memset(dest, 0, len) + # endif + #else +! void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); +! int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); +! void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len)); + #endif + + /* Diagnostic functions */ + #ifdef ZLIB_DEBUG + # include + extern int ZLIB_INTERNAL z_verbose; +! extern void ZLIB_INTERNAL z_error OF((char *m)); + # define Assert(cond,msg) {if(!(cond)) z_error(msg);} + # define Trace(x) {if (z_verbose>=0) fprintf x ;} + # define Tracev(x) {if (z_verbose>0) fprintf x ;} +--- 228,243 ---- + # define zmemzero(dest, len) memset(dest, 0, len) + # endif + #else +! void ZLIB_INTERNAL zmemcpy (Bytef* dest, const Bytef* source, uInt len); +! int ZLIB_INTERNAL zmemcmp (const Bytef* s1, const Bytef* s2, uInt len); +! void ZLIB_INTERNAL zmemzero (Bytef* dest, uInt len); + #endif + + /* Diagnostic functions */ + #ifdef ZLIB_DEBUG + # include + extern int ZLIB_INTERNAL z_verbose; +! extern void ZLIB_INTERNAL z_error (char *m); + # define Assert(cond,msg) {if(!(cond)) z_error(msg);} + # define Trace(x) {if (z_verbose>=0) fprintf x ;} + # define Tracev(x) {if (z_verbose>0) fprintf x ;} +*************** +*** 254,262 **** + #endif + + #ifndef Z_SOLO +! voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, +! unsigned size)); +! void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); + #endif + + #define ZALLOC(strm, items, size) \ +--- 254,262 ---- + #endif + + #ifndef Z_SOLO +! voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, +! unsigned size); +! void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr); + #endif + + #define ZALLOC(strm, items, size) \ diff --git a/sandboxed_api/bazel/filewrapper.cc b/sandboxed_api/bazel/filewrapper.cc new file mode 100644 index 0000000..1f97680 --- /dev/null +++ b/sandboxed_api/bazel/filewrapper.cc @@ -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 +#include +#include +#include +#include +#include + +#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 + +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> 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; +} diff --git a/sandboxed_api/bazel/filewrapper_test.cc b/sandboxed_api/bazel/filewrapper_test.cc new file mode 100644 index 0000000..f8c1401 --- /dev/null +++ b/sandboxed_api/bazel/filewrapper_test.cc @@ -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 + +#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 diff --git a/sandboxed_api/bazel/proto.bzl b/sandboxed_api/bazel/proto.bzl new file mode 100644 index 0000000..a074088 --- /dev/null +++ b/sandboxed_api/bazel/proto.bzl @@ -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 + ) diff --git a/sandboxed_api/bazel/repositories.bzl b/sandboxed_api/bazel/repositories.bzl new file mode 100644 index 0000000..12bef8b --- /dev/null +++ b/sandboxed_api/bazel/repositories.bzl @@ -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, +) diff --git a/sandboxed_api/bazel/sapi.bzl b/sandboxed_api/bazel/sapi.bzl new file mode 100644 index 0000000..f2a53ed --- /dev/null +++ b/sandboxed_api/bazel/sapi.bzl @@ -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' > $@ + """, + ) diff --git a/sandboxed_api/bazel/testdata/filewrapper_embedded.bin b/sandboxed_api/bazel/testdata/filewrapper_embedded.bin new file mode 100644 index 0000000..5427935 Binary files /dev/null and b/sandboxed_api/bazel/testdata/filewrapper_embedded.bin differ diff --git a/sandboxed_api/call.h b/sandboxed_api/call.h new file mode 100644 index 0000000..1da3fd7 --- /dev/null +++ b/sandboxed_api/call.h @@ -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 + +#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_ diff --git a/sandboxed_api/client.cc b/sandboxed_api/client.cc new file mode 100644 index 0000000..6628428 --- /dev/null +++ b/sandboxed_api/client.cc @@ -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 +#include + +#include +#include +#include +#include + +#include +#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 +#endif +#include +#include + +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(call.args[i].arg_int)); + } else { + if (call.arg_type[i] == v::Type::kFloat) { + arg_values_[i] = + reinterpret_cast(&call.args[i].arg_float); + } else { + arg_values_[i] = reinterpret_cast(&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 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(arg_types_); } + void** arg_values() const { return const_cast(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> 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(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(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(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(calloc(1, static_cast(size))); +#else + ret->int_val = reinterpret_cast(malloc(static_cast(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(ptr)); +#endif + ret->ret_type = v::Type::kPointer; + ret->int_val = reinterpret_cast( + realloc(const_cast(reinterpret_cast(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(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(reinterpret_cast(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(Error::kDlOpen); + return; + } + + ret->int_val = reinterpret_cast(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 +static T BytesAs(const std::vector& bytes) { + static_assert(std::is_trivial(), + "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 bytes; + + CHECK(comms->RecvTLV(&tag, &bytes)); + + FuncRet ret{}; + ret.ret_type = v::Type::kVoid; + ret.int_val = static_cast(Error::kUnset); + ret.success = false; + + switch (tag) { + case comms::kMsgCall: + VLOG(1) << "Client::kMsgCall"; + HandleCallMsg(BytesAs(bytes), &ret); + break; + case comms::kMsgAllocate: + VLOG(1) << "Client::kMsgAllocate"; + HandleAllocMsg(BytesAs(bytes), &ret); + break; + case comms::kMsgReallocate: + VLOG(1) << "Client::kMsgReallocate"; + { + auto req = BytesAs(bytes); + HandleReallocMsg(static_cast(req.old_addr), + static_cast(req.size), &ret); + } + break; + case comms::kMsgFree: + VLOG(1) << "Client::kMsgFree"; + HandleFreeMsg(BytesAs(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(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(bytes), &ret); + break; + case comms::kMsgClose: + VLOG(1) << "Received Client::kMsgClose message"; + HandleCloseFd(comms, BytesAs(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(&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"; +} diff --git a/sandboxed_api/docs/examples.md b/sandboxed_api/docs/examples.md new file mode 100644 index 0000000..6b00100 --- /dev/null +++ b/sandboxed_api/docs/examples.md @@ -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). diff --git a/sandboxed_api/docs/getting-started.md b/sandboxed_api/docs/getting-started.md new file mode 100644 index 0000000..ca7e765 --- /dev/null +++ b/sandboxed_api/docs/getting-started.md @@ -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). diff --git a/sandboxed_api/docs/host-code.md b/sandboxed_api/docs/host-code.md new file mode 100644 index 0000000..4023839 --- /dev/null +++ b/sandboxed_api/docs/host-code.md @@ -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 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). diff --git a/sandboxed_api/docs/howitworks.md b/sandboxed_api/docs/howitworks.md new file mode 100644 index 0000000..ac0f8de --- /dev/null +++ b/sandboxed_api/docs/howitworks.md @@ -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. diff --git a/sandboxed_api/docs/images/playing-in-sand.png b/sandboxed_api/docs/images/playing-in-sand.png new file mode 100644 index 0000000..38a568d Binary files /dev/null and b/sandboxed_api/docs/images/playing-in-sand.png differ diff --git a/sandboxed_api/docs/images/sapi-overview.png b/sandboxed_api/docs/images/sapi-overview.png new file mode 100644 index 0000000..4248700 Binary files /dev/null and b/sandboxed_api/docs/images/sapi-overview.png differ diff --git a/sandboxed_api/docs/library.md b/sandboxed_api/docs/library.md new file mode 100644 index 0000000..f0e3040 --- /dev/null +++ b/sandboxed_api/docs/library.md @@ -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). diff --git a/sandboxed_api/docs/sandbox-overview.md b/sandboxed_api/docs/sandbox-overview.md new file mode 100644 index 0000000..daf2fa1 --- /dev/null +++ b/sandboxed_api/docs/sandbox-overview.md @@ -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. diff --git a/sandboxed_api/docs/variables.md b/sandboxed_api/docs/variables.md new file mode 100644 index 0000000..954a370 --- /dev/null +++ b/sandboxed_api/docs/variables.md @@ -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` 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) diff --git a/sandboxed_api/embed_file.cc b/sandboxed_api/embed_file.cc new file mode 100644 index 0000000..c7a4630 --- /dev/null +++ b/sandboxed_api/embed_file.cc @@ -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 +#include +#include +#include +#include + +#include +#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 diff --git a/sandboxed_api/embed_file.h b/sandboxed_api/embed_file.h new file mode 100644 index 0000000..b65dfaa --- /dev/null +++ b/sandboxed_api/embed_file.h @@ -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 +#include + +#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 file_tocs_ + GUARDED_BY(file_tocs_mutex_); + absl::Mutex file_tocs_mutex_; +}; + +} // namespace sapi + +#endif // SANDBOXED_API_EMBED_FILE_H_ diff --git a/sandboxed_api/examples/stringop/BUILD.bazel b/sandboxed_api/examples/stringop/BUILD.bazel new file mode 100644 index 0000000..7bc349f --- /dev/null +++ b/sandboxed_api/examples/stringop/BUILD.bazel @@ -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", + ], +) diff --git a/sandboxed_api/examples/stringop/lib/BUILD.bazel b/sandboxed_api/examples/stringop/lib/BUILD.bazel new file mode 100644 index 0000000..5610864 --- /dev/null +++ b/sandboxed_api/examples/stringop/lib/BUILD.bazel @@ -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"], +) diff --git a/sandboxed_api/examples/stringop/lib/sandbox.h b/sandboxed_api/examples/stringop/lib/sandbox.h new file mode 100644 index 0000000..ac02522 --- /dev/null +++ b/sandboxed_api/examples/stringop/lib/sandbox.h @@ -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 +#include + +#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 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_ diff --git a/sandboxed_api/examples/stringop/lib/stringop.cc b/sandboxed_api/examples/stringop/lib/stringop.cc new file mode 100644 index 0000000..382d80a --- /dev/null +++ b/sandboxed_api/examples/stringop/lib/stringop.cc @@ -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 +#include +#include + +#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(malloc(input->size)); + const char* src_buf = static_cast(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(malloc(2 * input->size)); + const char* src_buf = static_cast(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); } diff --git a/sandboxed_api/examples/stringop/lib/stringop_params.proto b/sandboxed_api/examples/stringop/lib/stringop_params.proto new file mode 100644 index 0000000..4648429 --- /dev/null +++ b/sandboxed_api/examples/stringop/lib/stringop_params.proto @@ -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; + } +} diff --git a/sandboxed_api/examples/stringop/main_stringop.cc b/sandboxed_api/examples/stringop/main_stringop.cc new file mode 100644 index 0000000..2501474 --- /dev/null +++ b/sandboxed_api/examples/stringop/main_stringop.cc @@ -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 +#include +#include + +#include +#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()); + EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status { + StringopApi f(sandbox); + stringop::StringDuplication proto; + proto.set_input("Hello"); + sapi::v::Proto 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()); + EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status { + StringopApi f(sandbox); + stringop::StringReverse proto; + proto.set_input("Hello"); + sapi::v::Proto 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()); + 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(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()); + 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(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(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(param.GetData()), + param.GetDataSize()); + TRANSACTION_FAIL_IF_NOT( + data == "FEDCBA0123456789", + "reverse_string() did not return the expected data"); + } + return sapi::OkStatus(); + }), + IsOk()); +} + +} // namespace diff --git a/sandboxed_api/examples/sum/BUILD.bazel b/sandboxed_api/examples/sum/BUILD.bazel new file mode 100644 index 0000000..d671633 --- /dev/null +++ b/sandboxed_api/examples/sum/BUILD.bazel @@ -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", + ], +) diff --git a/sandboxed_api/examples/sum/lib/BUILD.bazel b/sandboxed_api/examples/sum/lib/BUILD.bazel new file mode 100644 index 0000000..945f7dd --- /dev/null +++ b/sandboxed_api/examples/sum/lib/BUILD.bazel @@ -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"], +) diff --git a/sandboxed_api/examples/sum/lib/sandbox.h b/sandboxed_api/examples/sum/lib/sandbox.h new file mode 100644 index 0000000..436c819 --- /dev/null +++ b/sandboxed_api/examples/sum/lib/sandbox.h @@ -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 +#include + +#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 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_ diff --git a/sandboxed_api/examples/sum/lib/sum.c b/sandboxed_api/examples/sum/lib/sum.c new file mode 100644 index 0000000..b021173 --- /dev/null +++ b/sandboxed_api/examples/sum/lib/sum.c @@ -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 +#include +#include +#include +#include + +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); +} diff --git a/sandboxed_api/examples/sum/lib/sum_cpp.cc b/sandboxed_api/examples/sum/lib/sum_cpp.cc new file mode 100644 index 0000000..be8a1d6 --- /dev/null +++ b/sandboxed_api/examples/sum/lib/sum_cpp.cc @@ -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 +#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(); +} diff --git a/sandboxed_api/examples/sum/lib/sum_params.proto b/sandboxed_api/examples/sum/lib/sum_params.proto new file mode 100644 index 0000000..1d8ac13 --- /dev/null +++ b/sandboxed_api/examples/sum/lib/sum_params.proto @@ -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]; +} diff --git a/sandboxed_api/examples/sum/main_sum.cc b/sandboxed_api/examples/sum/main_sum.cc new file mode 100644 index 0000000..d260af3 --- /dev/null +++ b/sandboxed_api/examples/sum/main_sum.cc @@ -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 +#include +#include + +#include +#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 {}; + +class SumTransaction : public sapi::Transaction { + public: + SumTransaction(std::unique_ptr 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 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(&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 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 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 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 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()}; + // 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 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(), /*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(), + /*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(), + /*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(), /*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; +} diff --git a/sandboxed_api/examples/zlib/BUILD.bazel b/sandboxed_api/examples/zlib/BUILD.bazel new file mode 100644 index 0000000..f6fdc98 --- /dev/null +++ b/sandboxed_api/examples/zlib/BUILD.bazel @@ -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", + ], +) diff --git a/sandboxed_api/examples/zlib/main_zlib.cc b/sandboxed_api/examples/zlib/main_zlib.cc new file mode 100644 index 0000000..d3c46d8 --- /dev/null +++ b/sandboxed_api/examples/zlib/main_zlib.cc @@ -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 +#include + +#include +#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 strm; + + constexpr int kChunk = 16384; + unsigned char in_[kChunk] = {0}; + unsigned char out_[kChunk] = {0}; + sapi::v::Array input(in_, kChunk); + sapi::v::Array 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 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(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(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; +} diff --git a/sandboxed_api/file_toc.h b/sandboxed_api/file_toc.h new file mode 100644 index 0000000..f48c74b --- /dev/null +++ b/sandboxed_api/file_toc.h @@ -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 + +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_ diff --git a/sandboxed_api/lenval_core.h b/sandboxed_api/lenval_core.h new file mode 100644 index 0000000..cc81bf3 --- /dev/null +++ b/sandboxed_api/lenval_core.h @@ -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 + +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_ diff --git a/sandboxed_api/proto_arg.proto b/sandboxed_api/proto_arg.proto new file mode 100644 index 0000000..af5de66 --- /dev/null +++ b/sandboxed_api/proto_arg.proto @@ -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; +} diff --git a/sandboxed_api/proto_helper.h b/sandboxed_api/proto_helper.h new file mode 100644 index 0000000..4c95975 --- /dev/null +++ b/sandboxed_api/proto_helper.h @@ -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 +#include + +#include +#include "sandboxed_api/proto_arg.pb.h" + +namespace sapi { + +template +std::vector 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 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 +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_ diff --git a/sandboxed_api/rpcchannel.cc b/sandboxed_api/rpcchannel.cc new file mode 100644 index 0000000..fb3fe92 --- /dev/null +++ b/sandboxed_api/rpcchannel.cc @@ -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 +#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(&call))) { + return sapi::UnavailableError("Sending TLV value failed"); + } + SAPI_ASSIGN_OR_RETURN(auto fret, Return(exp_type)); + *ret = fret; + return sapi::OkStatus(); +} + +sapi::StatusOr 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(&sz))) { + return sapi::UnavailableError("Sending TLV value failed"); + } + + SAPI_ASSIGN_OR_RETURN(auto fret, Return(v::Type::kPointer)); + *addr = reinterpret_cast(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(old_addr); + req.size = size; + + if (!comms_->SendTLV(comms::kMsgReallocate, sizeof(comms::ReallocRequest), + reinterpret_cast(&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(fret.int_val); + return sapi::OkStatus(); +} + +sapi::Status RPCChannel::Free(void* addr) { + absl::MutexLock lock(&mutex_); + uint64_t remote = reinterpret_cast(addr); + if (!comms_->SendTLV(comms::kMsgFree, sizeof(remote), + reinterpret_cast(&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(symname))) { + return sapi::UnavailableError("Sending TLV value failed"); + } + + SAPI_ASSIGN_OR_RETURN(auto fret, Return(v::Type::kPointer)); + *addr = reinterpret_cast(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(&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(&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(&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(&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 diff --git a/sandboxed_api/rpcchannel.h b/sandboxed_api/rpcchannel.h new file mode 100644 index 0000000..4465d4b --- /dev/null +++ b/sandboxed_api/rpcchannel.h @@ -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 + +#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 Return(v::Type exp_type); + + sandbox2::Comms* comms_; // Owned by sandbox2; + absl::Mutex mutex_; +}; + +} // namespace sapi + +#endif // SANDBOXED_API_RPCCHANNEL_H_ diff --git a/sandboxed_api/sandbox.cc b/sandboxed_api/sandbox.cc new file mode 100644 index 0000000..056d890 --- /dev/null +++ b/sandboxed_api/sandbox.cc @@ -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 +#include +#include + +#include +#include +#include + +#include +#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 args{lib_path}; + // Additional arguments, if needed. + GetArgs(&args); + std::vector envs{}; + // Additional envvars, if needed. + GetEnvs(&envs); + + forkserver_executor_ = + (embed_lib_fd >= 0) + ? absl::make_unique(embed_lib_fd, args, envs) + : absl::make_unique(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(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(std::move(executor), + std::move(s2p)); + auto res = s2_->RunAsync(); + + comms_ = s2_->comms(); + pid_ = s2_->GetPid(); + + rpc_channel_ = absl::make_unique(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(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(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 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(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(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(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 Sandbox::ModifyPolicy( + sandbox2::PolicyBuilder* builder) { + return builder->BuildOrDie(); +} + +} // namespace sapi diff --git a/sandboxed_api/sandbox.h b/sandboxed_api/sandbox.h new file mode 100644 index 0000000..f1a8bd7 --- /dev/null +++ b/sandboxed_api/sandbox.h @@ -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 +#include + +#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 + ::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)...}); + } + ::sapi::Status Call(const std::string& func, v::Callable* ret, + std::initializer_list 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* 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 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* 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 fork_client_; + std::unique_ptr forkserver_executor_; + + // The main sandbox2::Sandbox2 object. + std::unique_ptr s2_; + + // Result of the most recent sandbox execution + sandbox2::Result result_; + + // Comms with the sandboxee. + sandbox2::Comms* comms_; + // RPCChannel object. + std::unique_ptr 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_ diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel new file mode 100644 index 0000000..2336e91 --- /dev/null +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -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", + ], +) diff --git a/sandboxed_api/sandbox2/README.md b/sandboxed_api/sandbox2/README.md new file mode 100644 index 0000000..be5fc9c --- /dev/null +++ b/sandboxed_api/sandbox2/README.md @@ -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. + diff --git a/sandboxed_api/sandbox2/bpfdisassembler.cc b/sandboxed_api/sandbox2/bpfdisassembler.cc new file mode 100644 index 0000000..86dc3cf --- /dev/null +++ b/sandboxed_api/sandbox2/bpfdisassembler.cc @@ -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 +#include +#include + +#include + +#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& 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 diff --git a/sandboxed_api/sandbox2/bpfdisassembler.h b/sandboxed_api/sandbox2/bpfdisassembler.h new file mode 100644 index 0000000..94b4b4c --- /dev/null +++ b/sandboxed_api/sandbox2/bpfdisassembler.h @@ -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 +#include + +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& prog); + +} // namespace bpf +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_BPFDISASSEMBLER_H_ diff --git a/sandboxed_api/sandbox2/buffer.cc b/sandboxed_api/sandbox2/buffer.cc new file mode 100644 index 0000000..4d39dcb --- /dev/null +++ b/sandboxed_api/sandbox2/buffer.cc @@ -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 +#include +#include + +#include + +#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> 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(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> 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 diff --git a/sandboxed_api/sandbox2/buffer.h b/sandboxed_api/sandbox2/buffer.h new file mode 100644 index 0000000..8b7d5c5 --- /dev/null +++ b/sandboxed_api/sandbox2/buffer.h @@ -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 +#include +#include + +#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> CreateFromFd(int fd); + + // Creates a new Buffer of the specified size, backed by a temporary file that + // will be immediately deleted. + static ::sapi::StatusOr> 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_ diff --git a/sandboxed_api/sandbox2/buffer_test.cc b/sandboxed_api/sandbox2/buffer_test.cc new file mode 100644 index 0000000..32c5c02 --- /dev/null +++ b/sandboxed_api/sandbox2/buffer_test.cc @@ -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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#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 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 args = {path, "1"}; + auto executor = absl::make_unique(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 args = {path, "2"}; + auto executor = absl::make_unique(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 diff --git a/sandboxed_api/sandbox2/client.cc b/sandboxed_api/sandbox2/client.cc new file mode 100644 index 0000000..6c38eb7 --- /dev/null +++ b/sandboxed_api/sandbox2/client.cc @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 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 bytes; + SAPI_RAW_CHECK(comms_->RecvBytes(&bytes), "receive bytes"); + policy_len_ = bytes.size(); + + policy_ = absl::make_unique(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(policy_len_ / sizeof(sock_filter)); + prog.filter = reinterpret_cast(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(&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(GetMappedFD(LogSink::kLogFDName)); +} + +NetworkProxyClient* Client::GetNetworkProxyClient() { + if (proxy_client_ == nullptr) { + proxy_client_ = absl::make_unique( + GetMappedFD(NetworkProxyClient::kFDName)); + } + return proxy_client_.get(); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/client.h b/sandboxed_api/sandbox2/client.h new file mode 100644 index 0000000..c395d11 --- /dev/null +++ b/sandboxed_api/sandbox2/client.h @@ -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 +#include +#include +#include + +#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 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_; + + // NetworkProxyClient that forwards network connection requests to the + // supervisor. + std::unique_ptr 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 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_ diff --git a/sandboxed_api/sandbox2/comms.cc b/sandboxed_api/sandbox2/comms.cc new file mode 100644 index 0000000..6eef77e --- /dev/null +++ b/sandboxed_api/sandbox2/comms.cc @@ -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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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(&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(&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(&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(&tag), sizeof(tag))) { + return false; + } + if (!Send(reinterpret_cast(&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(tlv.value.data()), tlv.value.size()); + return true; +} + +bool Comms::SendString(const std::string& v) { + return SendTLV(kTagString, v.length(), + reinterpret_cast(v.c_str())); +} + +bool Comms::RecvBytes(std::vector* 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& 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(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(&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(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(fd_msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + + int* fds = reinterpret_cast(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(&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(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(tag), sizeof(*tag))) { + return false; + } + if (!Recv(reinterpret_cast(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* 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(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 diff --git a/sandboxed_api/sandbox2/comms.h b/sandboxed_api/sandbox2/comms.h new file mode 100644 index 0000000..5cf9696 --- /dev/null +++ b/sandboxed_api/sandbox2/comms.h @@ -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 +#include + +#include +#include +#include +#include + +#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::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* 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* buffer); + bool SendBytes(const uint8_t* v, uint64_t len); + bool SendBytes(const std::vector& 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 + bool RecvStatus(StatusT* status); + template + 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 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 + bool RecvIntGeneric(T* output, uint32_t tag) { + return RecvInt(output, sizeof(T), tag); + } + + template + bool SendGeneric(T value, uint32_t tag) { + return SendTLV(tag, sizeof(T), reinterpret_cast(&value)); + } +}; + +template +bool Comms::RecvStatus(StatusT* status) { + sapi::StatusProto proto; + if (!RecvProtoBuf(&proto)) { + return false; + } + *status = sapi::MakeStatusFromProto(proto); + return true; +} + +template +bool Comms::SendStatus(const StatusT& status) { + sapi::StatusProto proto; + sapi::SaveStatusToProto(status, &proto); + return SendProtoBuf(proto); +} + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_COMMS_H_ diff --git a/sandboxed_api/sandbox2/comms_test.cc b/sandboxed_api/sandbox2/comms_test.cc new file mode 100644 index 0000000..1ec1b46 --- /dev/null +++ b/sandboxed_api/sandbox2/comms_test.cc @@ -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 +#include +#include +#include +#include +#include +#include // NOLINT(build/c++11) +#include + +#include +#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; + +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(ts1.tv_sec), static_cast(ts1.tv_nsec), + static_cast(ts2.tv_sec), static_cast(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 buffer; + ASSERT_THAT(comms->RecvBytes(&buffer), IsTrue()); + EXPECT_THAT(buffer.size(), Eq(1024 * 1024)); + }; + auto b = [](Comms* comms) { + // Send 1M bytes. + std::vector 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 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 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 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 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 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 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 buffer; + ASSERT_THAT(comms->RecvBytes(&buffer), IsTrue()); + ASSERT_THAT(comms->SendBytes(buffer), IsTrue()); + }; + auto b = [](Comms* comms) { + const std::vector request = {0, 1, 2, 3, 7}; + ASSERT_THAT(comms->SendBytes(request), IsTrue()); + + std::vector 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("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 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 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 diff --git a/sandboxed_api/sandbox2/comms_test.proto b/sandboxed_api/sandbox2/comms_test.proto new file mode 100644 index 0000000..0bcfbe4 --- /dev/null +++ b/sandboxed_api/sandbox2/comms_test.proto @@ -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; +}; diff --git a/sandboxed_api/sandbox2/deathrattle_fatalmsg.proto b/sandboxed_api/sandbox2/deathrattle_fatalmsg.proto new file mode 100644 index 0000000..8575d96 --- /dev/null +++ b/sandboxed_api/sandbox2/deathrattle_fatalmsg.proto @@ -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; +} diff --git a/sandboxed_api/sandbox2/docs/examples.md b/sandboxed_api/sandbox2/docs/examples.md new file mode 100644 index 0000000..237b781 --- /dev/null +++ b/sandboxed_api/sandbox2/docs/examples.md @@ -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). diff --git a/sandboxed_api/sandbox2/docs/faq.md b/sandboxed_api/sandbox2/docs/faq.md new file mode 100644 index 0000000..188b699 --- /dev/null +++ b/sandboxed_api/sandbox2/docs/faq.md @@ -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]`. diff --git a/sandboxed_api/sandbox2/docs/getting-started.md b/sandboxed_api/sandbox2/docs/getting-started.md new file mode 100644 index 0000000..f44ea16 --- /dev/null +++ b/sandboxed_api/sandbox2/docs/getting-started.md @@ -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 args = {path}; // args[0] will become the sandboxed + // process' argv[0], typically the path + // to the binary. +auto executor = absl::make_unique(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 args = {path}; +auto executor = absl::make_unique(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 args = {path}; +auto fork_executor = absl::make_unique(path, args); +fork_executor->StartForkServer(); + +// Initialize Executor with Comms channel to the ForkServer +auto executor = absl::make_unique( + 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 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(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. diff --git a/sandboxed_api/sandbox2/docs/howitworks.md b/sandboxed_api/sandbox2/docs/howitworks.md new file mode 100644 index 0000000..1d9df32 --- /dev/null +++ b/sandboxed_api/sandbox2/docs/howitworks.md @@ -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. diff --git a/sandboxed_api/sandbox2/examples/crc4/BUILD.bazel b/sandboxed_api/sandbox2/examples/crc4/BUILD.bazel new file mode 100644 index 0000000..9f55fda --- /dev/null +++ b/sandboxed_api/sandbox2/examples/crc4/BUILD.bazel @@ -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", + ], +) diff --git a/sandboxed_api/sandbox2/examples/crc4/crc4bin.cc b/sandboxed_api/sandbox2/examples/crc4/crc4bin.cc new file mode 100644 index 0000000..c0b10a4 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/crc4/crc4bin.cc @@ -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 + +#include + +#include +#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 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 ComputeCRC4 = ComputeCRC4Impl; + + uint32_t crc4 = ComputeCRC4(buffer.data(), buffer.size()); + + if (!comms.SendUint32(crc4)) { + return 2; + } + return 0; +} diff --git a/sandboxed_api/sandbox2/examples/crc4/crc4sandbox.cc b/sandboxed_api/sandbox2/examples/crc4/crc4sandbox.cc new file mode 100644 index 0000000..ce3c644 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/crc4/crc4sandbox.cc @@ -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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#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 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(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 args = {path}; + if (absl::GetFlag(FLAGS_call_syscall_not_allowed)) { + args.push_back("-call_syscall_not_allowed"); + } + std::vector envs = {}; + auto executor = absl::make_unique(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; +} diff --git a/sandboxed_api/sandbox2/examples/crc4/crc4sandbox_test.cc b/sandboxed_api/sandbox2/examples/crc4/crc4sandbox_test.cc new file mode 100644 index 0000000..edeac4f --- /dev/null +++ b/sandboxed_api/sandbox2/examples/crc4/crc4sandbox_test.cc @@ -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 + +#include + +#include +#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 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 diff --git a/sandboxed_api/sandbox2/examples/custom_fork/BUILD.bazel b/sandboxed_api/sandbox2/examples/custom_fork/BUILD.bazel new file mode 100644 index 0000000..c31b6e7 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/custom_fork/BUILD.bazel @@ -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", + ], +) diff --git a/sandboxed_api/sandbox2/examples/custom_fork/custom_fork_bin.cc b/sandboxed_api/sandbox2/examples/custom_fork/custom_fork_bin.cc new file mode 100644 index 0000000..a08ba21 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/custom_fork/custom_fork_bin.cc @@ -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 + +#include + +#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); +} diff --git a/sandboxed_api/sandbox2/examples/custom_fork/custom_fork_sandbox.cc b/sandboxed_api/sandbox2/examples/custom_fork/custom_fork_sandbox.cc new file mode 100644 index 0000000..ee76145 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/custom_fork/custom_fork_sandbox.cc @@ -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 + +#include +#include +#include +#include +#include + +#include +#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 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(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 args = {path}; + std::vector envs = {}; + auto fork_executor = absl::make_unique(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. + // + // 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; +} diff --git a/sandboxed_api/sandbox2/examples/static/BUILD.bazel b/sandboxed_api/sandbox2/examples/static/BUILD.bazel new file mode 100644 index 0000000..cf28b57 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/static/BUILD.bazel @@ -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, +) diff --git a/sandboxed_api/sandbox2/examples/static/static_bin.cc b/sandboxed_api/sandbox2/examples/static/static_bin.cc new file mode 100644 index 0000000..32664ea --- /dev/null +++ b/sandboxed_api/sandbox2/examples/static/static_bin.cc @@ -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 +#include + +#include +#include +#include + +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; +} diff --git a/sandboxed_api/sandbox2/examples/static/static_sandbox.cc b/sandboxed_api/sandbox2/examples/static/static_sandbox.cc new file mode 100644 index 0000000..cb442da --- /dev/null +++ b/sandboxed_api/sandbox2/examples/static/static_sandbox.cc @@ -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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#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 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 args = {path}; + auto executor = absl::make_unique(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; +} diff --git a/sandboxed_api/sandbox2/examples/tool/BUILD.bazel b/sandboxed_api/sandbox2/examples/tool/BUILD.bazel new file mode 100644 index 0000000..4cc1ab4 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/tool/BUILD.bazel @@ -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", + ], +) diff --git a/sandboxed_api/sandbox2/examples/tool/sandbox2tool.cc b/sandboxed_api/sandbox2/examples/tool/sandbox2tool.cc new file mode 100644 index 0000000..f83a3b4 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/tool/sandbox2tool.cc @@ -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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#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 args; + sandbox2::util::CharPtrArrToVecString(&argv[1], &args); + + // Pass the current environ pointer, depending on the flag. + std::vector envp; + if (absl::GetFlag(FLAGS_sandbox2tool_keep_env)) { + sandbox2::util::CharPtrArrToVecString(environ, &envp); + } + auto executor = absl::make_unique(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 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; +} diff --git a/sandboxed_api/sandbox2/examples/zlib/BUILD.bazel b/sandboxed_api/sandbox2/examples/zlib/BUILD.bazel new file mode 100644 index 0000000..17294f3 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/zlib/BUILD.bazel @@ -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"], +) diff --git a/sandboxed_api/sandbox2/examples/zlib/zpipe.c b/sandboxed_api/sandbox2/examples/zlib/zpipe.c new file mode 100644 index 0000000..83535d1 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/zlib/zpipe.c @@ -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 +#include +#include +#include "zlib.h" + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# 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; + } +} diff --git a/sandboxed_api/sandbox2/examples/zlib/zpipe_sandbox.cc b/sandboxed_api/sandbox2/examples/zlib/zpipe_sandbox.cc new file mode 100644 index 0000000..7071a9f --- /dev/null +++ b/sandboxed_api/sandbox2/examples/zlib/zpipe_sandbox.cc @@ -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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#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 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 args = {path}; + if (absl::GetFlag(FLAGS_decompress)) { + args.push_back("-d"); + } + std::vector envs = {}; + auto executor = absl::make_unique(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; +} diff --git a/sandboxed_api/sandbox2/executor.cc b/sandboxed_api/sandbox2/executor.cc new file mode 100644 index 0000000..f2a4d02 --- /dev/null +++ b/sandboxed_api/sandbox2/executor.cc @@ -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 +#include +#include +#include + +#include +#include + +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include +#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& argv, + const std::vector& 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 Executor::CopyEnviron() { + std::vector environ_copy; + util::CharPtrArrToVecString(environ, &environ_copy); + return environ_copy; +} + +pid_t Executor::StartSubProcess(int32_t clone_flags, const Namespace* ns, + const std::vector* 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 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(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 diff --git a/sandboxed_api/sandbox2/executor.h b/sandboxed_api/sandbox2/executor.h new file mode 100644 index 0000000..bed3858 --- /dev/null +++ b/sandboxed_api/sandbox2/executor.h @@ -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 +#include +#include +#include +#include +#include + +#include +#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& 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& argv, + const std::vector& 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& argv, + const std::vector& 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 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& argv, + const std::vector& envp, bool enable_sandboxing_pre_execve, + pid_t libunwind_sbox_for_pid, ForkClient* fork_client); + + // Creates a copy of the environment + static std::vector 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* 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 argv_; + std::vector 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_ diff --git a/sandboxed_api/sandbox2/forkingclient.cc b/sandboxed_api/sandbox2/forkingclient.cc new file mode 100644 index 0000000..0611b00 --- /dev/null +++ b/sandboxed_api/sandbox2/forkingclient.cc @@ -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 + +#include +#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(comms_); + } + return fork_server_worker_->ServeRequest(); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/forkingclient.h b/sandboxed_api/sandbox2/forkingclient.h new file mode 100644 index 0000000..77f4e4c --- /dev/null +++ b/sandboxed_api/sandbox2/forkingclient.h @@ -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 +#include + +#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 fork_server_worker_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_FORKINGCLIENT_H_ diff --git a/sandboxed_api/sandbox2/forkserver.cc b/sandboxed_api/sandbox2/forkserver.cc new file mode 100644 index 0000000..66a0d46 --- /dev/null +++ b/sandboxed_api/sandbox2/forkserver.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#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); + } + + // 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); +} + +void ForkServer::PrepareExecveArgs(const ForkRequest& request, + std::vector* args, + std::vector* 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 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(&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 args; + std::vector envs; + const char** argv = nullptr; + const char** envp = nullptr; + if (will_execve) { + PrepareExecveArgs(request, &args, &envs); + } + + SanitizeEnvironment(client_fd); + + std::set 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(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(execve_fd), + reinterpret_cast(""), reinterpret_cast(argv), + reinterpret_cast(envp), static_cast(AT_EMPTY_PATH), + reinterpret_cast(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 diff --git a/sandboxed_api/sandbox2/forkserver.h b/sandboxed_api/sandbox2/forkserver.h new file mode 100644 index 0000000..635e6a7 --- /dev/null +++ b/sandboxed_api/sandbox2/forkserver.h @@ -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 +#include +#include + +#include +#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* args, + std::vector* 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_ diff --git a/sandboxed_api/sandbox2/forkserver.proto b/sandboxed_api/sandbox2/forkserver.proto new file mode 100644 index 0000000..60cfe52 --- /dev/null +++ b/sandboxed_api/sandbox2/forkserver.proto @@ -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; +} diff --git a/sandboxed_api/sandbox2/forkserver_test.cc b/sandboxed_api/sandbox2/forkserver_test.cc new file mode 100644 index 0000000..6cc87f8 --- /dev/null +++ b/sandboxed_api/sandbox2/forkserver_test.cc @@ -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 +#include +#include +#include +#include + +#include +#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 diff --git a/sandboxed_api/sandbox2/global_forkclient.cc b/sandboxed_api/sandbox2/global_forkclient.cc new file mode 100644 index 0000000..3f54eb0 --- /dev/null +++ b/sandboxed_api/sandbox2/global_forkclient.cc @@ -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 +#include +#include + +#include +#include + +#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_ initializer instead. +ABSL_ATTRIBUTE_UNUSED +__attribute__((constructor)) static void StartSandbox2Forkserver() { + sandbox2::StartGlobalForkServer(); +} diff --git a/sandboxed_api/sandbox2/global_forkclient.h b/sandboxed_api/sandbox2/global_forkclient.h new file mode 100644 index 0000000..3675873 --- /dev/null +++ b/sandboxed_api/sandbox2/global_forkclient.h @@ -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_ diff --git a/sandboxed_api/sandbox2/ipc.cc b/sandboxed_api/sandbox2/ipc.cc new file mode 100644 index 0000000..32120b8 --- /dev/null +++ b/sandboxed_api/sandbox2/ipc.cc @@ -0,0 +1,117 @@ +// 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::IPC class + +#include "sandboxed_api/sandbox2/ipc.h" + +#include +#include + +#include // NOLINT(build/c++11) + +#include +#include "absl/memory/memory.h" +#include "sandboxed_api/sandbox2/logserver.h" +#include "sandboxed_api/sandbox2/logsink.h" +#include "sandboxed_api/sandbox2/network_proxy_client.h" +#include "sandboxed_api/sandbox2/network_proxy_server.h" + +namespace sandbox2 { + +void IPC::SetUpServerSideComms(int fd) { + comms_ = absl::make_unique(fd); +} + +void IPC::MapFd(int local_fd, int remote_fd) { + VLOG(3) << "Will send: " << local_fd << ", to overwrite: " << remote_fd; + + fd_map_.push_back(std::make_tuple(local_fd, remote_fd, "")); +} + +int IPC::ReceiveFd(int remote_fd) { return ReceiveFd(remote_fd, ""); } + +int IPC::ReceiveFd(absl::string_view name) { return ReceiveFd(-1, name); } + +int IPC::ReceiveFd(int remote_fd, absl::string_view name) { + int sv[2]; + if (socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) == -1) { + PLOG(FATAL) << "socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)"; + } + + VLOG(3) << "Created a socketpair (" << sv[0] << "/" << sv[1] << "), " + << "which will overwrite remote_fd: " << remote_fd; + + fd_map_.push_back(std::make_tuple(sv[1], remote_fd, std::string(name))); + + return sv[0]; +} + +bool IPC::SendFdsOverComms() { + if (!(comms_->SendUint32(fd_map_.size()))) { + LOG(ERROR) << "Couldn't send IPC fd size"; + return false; + } + + for (const auto& fd_tuple : fd_map_) { + if (!(comms_->SendInt32(std::get<1>(fd_tuple)))) { + LOG(ERROR) << "SendInt32: Couldn't send " << std::get<1>(fd_tuple); + return false; + } + if (!(comms_->SendFD(std::get<0>(fd_tuple)))) { + LOG(ERROR) << "SendFd: Couldn't send " << std::get<0>(fd_tuple); + return false; + } + + if (!(comms_->SendString(std::get<2>(fd_tuple)))) { + LOG(ERROR) << "SendString: Couldn't send " << std::get<2>(fd_tuple); + return false; + } + + VLOG(3) << "IPC: local_fd: " << std::get<0>(fd_tuple) + << ", remote_fd: " << std::get<1>(fd_tuple) << " sent"; + } + + return true; +} + +void IPC::InternalCleanupFdMap() { + for (const auto& fd_tuple : fd_map_) { + close(std::get<0>(fd_tuple)); + } + fd_map_.clear(); +} + +void IPC::EnableLogServer() { + int fd = ReceiveFd(LogSink::kLogFDName); + auto logger = [fd] { + LogServer log_server(fd); + log_server.Run(); + }; + std::thread log_thread{logger}; + log_thread.detach(); +} + +void IPC::EnableNetworkProxyServer() { + int fd = ReceiveFd(NetworkProxyClient::kFDName); + + auto proxy_server = [fd]() { + NetworkProxyServer network_proxy_server(fd); + network_proxy_server.Run(); + }; + std::thread proxy_thread{proxy_server}; + proxy_thread.detach(); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/ipc.h b/sandboxed_api/sandbox2/ipc.h new file mode 100644 index 0000000..b1b66d7 --- /dev/null +++ b/sandboxed_api/sandbox2/ipc.h @@ -0,0 +1,91 @@ +// 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::IPC class provides routines for exchanging data between sandbox +// and the sandboxee. + +#ifndef SANDBOXED_API_SANDBOX2_IPC_H_ +#define SANDBOXED_API_SANDBOX2_IPC_H_ + +#include +#include +#include +#include + +#include "absl/base/macros.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/comms.h" + +namespace sandbox2 { + +class IPC final { + public: + IPC() = default; + + IPC(const IPC&) = delete; + IPC& operator=(const IPC&) = delete; + + ~IPC() { InternalCleanupFdMap(); } + Comms* comms() const { return comms_.get(); } + + // Marks local_fd so that it should be sent to the remote process (sandboxee), + // and duplicated onto remote_fd in it. The local_fd will be closed after + // being sent (in SendFdsOverComms which is called by the Monitor class), so + // it should not be used from that point on. + void MapFd(int local_fd, int remote_fd); + + // Creates and returns a socketpair endpoint. The other endpoint of the + // socketpair is marked as to be sent to the remote process (sandboxee) with + // SendFdsOverComms() as with MapFd(). + // If a name is specified, uses the Client::GetMappedFD api to retrieve the + // corresponding file descriptor in the sandboxee. + int ReceiveFd(int remote_fd, absl::string_view name); + int ReceiveFd(int remote_fd); + int ReceiveFd(absl::string_view name); + + // Enable sandboxee logging, this will start a thread that waits for log + // messages from the sandboxee. You'll also have to call + // Client::SendLogsToSupervisor in the sandboxee. + void EnableLogServer(); + + // Enable network proxy server, this will start a thread in the sandbox + // that waits for connection requests from the sandboxee. + void EnableNetworkProxyServer(); + + private: + friend class Executor; + friend class Monitor; + friend class IpcPeer; // For testing + + // Uses a pre-connected file descriptor. + void SetUpServerSideComms(int fd); + + // Sends file descriptors to the sandboxee. Close the local FDs (e.g. passed + // in MapFd()) - they cannot be used anymore. + bool SendFdsOverComms(); + + void InternalCleanupFdMap(); + + // Tuple of file descriptor pairs which will be sent to the sandboxee: in the + // form of tuple: local_fd: local fd which should be sent + // to sandboxee, remote_fd: it will be overwritten by local_fd. + std::vector> fd_map_; + + // Comms channel used to exchange data with the sandboxee. + std::unique_ptr comms_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_IPC_H_ diff --git a/sandboxed_api/sandbox2/ipc_test.cc b/sandboxed_api/sandbox2/ipc_test.cc new file mode 100644 index 0000000..97ce506 --- /dev/null +++ b/sandboxed_api/sandbox2/ipc_test.cc @@ -0,0 +1,115 @@ +// 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/ipc.h" + +#include + +#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/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/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/util/status_matchers.h" + +namespace sandbox2 { +namespace { + +constexpr int kPreferredIpcFd = 812; + +// This test verifies that mapping fds by name works if the sandbox is enabled +// before execve. +TEST(IPCTest, MapFDByNamePreExecve) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/ipc"); + std::vector args = {path, "1", std::to_string(kPreferredIpcFd)}; + auto executor = absl::make_unique(path, args); + Comms comms(executor->ipc()->ReceiveFd(kPreferredIpcFd, "ipc_test")); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + + Sandbox2 s2(std::move(executor), std::move(policy)); + s2.RunAsync(); + + ASSERT_TRUE(comms.SendString("hello")); + std::string resp; + ASSERT_TRUE(comms.RecvString(&resp)); + + ASSERT_EQ(resp, "world"); + + auto result = s2.AwaitResult(); + + ASSERT_EQ(result.final_status(), Result::OK); + ASSERT_EQ(result.reason_code(), 0); +} + +// This test verifies that mapping fds by name works if SandboxMeHere() is +// called by the sandboxee. +TEST(IPCTest, MapFDByNamePostExecve) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/ipc"); + std::vector args = {path, "2", std::to_string(kPreferredIpcFd)}; + auto executor = absl::make_unique(path, args); + executor->set_enable_sandbox_before_exec(false); + Comms comms(executor->ipc()->ReceiveFd(kPreferredIpcFd, "ipc_test")); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + + Sandbox2 s2(std::move(executor), std::move(policy)); + s2.RunAsync(); + + ASSERT_TRUE(comms.SendString("hello")); + std::string resp; + ASSERT_TRUE(comms.RecvString(&resp)); + + ASSERT_EQ(resp, "world"); + + auto result = s2.AwaitResult(); + + ASSERT_EQ(result.final_status(), Result::OK); + ASSERT_EQ(result.reason_code(), 0); +} + +TEST(IPCTest, NoMappedFDsPreExecve) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/ipc"); + std::vector args = {path, "3"}; + auto executor = absl::make_unique(path, args); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); + ASSERT_EQ(result.reason_code(), 0); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/limits.h b/sandboxed_api/sandbox2/limits.h new file mode 100644 index 0000000..147e051 --- /dev/null +++ b/sandboxed_api/sandbox2/limits.h @@ -0,0 +1,152 @@ +// 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::Limits class defined various client- and sandbox- side limits +// which are applied to the execution process of sandboxees. + +#ifndef SANDBOXED_API_SANDBOX2_LIMITS_H_ +#define SANDBOXED_API_SANDBOX2_LIMITS_H_ + +#include +#include +#include + +#include "absl/base/macros.h" +#include "absl/time/time.h" + +namespace sandbox2 { + +class Limits final { + public: + Limits() { + set_rlimit_as(kIniRLimAS); + set_rlimit_cpu(kIniRLimCPU); + set_rlimit_fsize(kIniRLimFSIZE); + set_rlimit_nofile(kIniRLimNOFILE); + set_rlimit_core(kIniRLimCORE); + set_walltime_limit(absl::Seconds(kIniWallTimeLimit)); + } + + Limits(const Limits&) = delete; + Limits& operator=(const Limits&) = delete; + + // Rlimit-s getters/setters. + // + // Use RLIM64_INFINITY for unlimited values, but remember that some of those + // cannot exceed system limits (e.g. RLIMIT_NOFILE). + const rlimit64& rlimit_as() const { return rlimit_as_; } + Limits& set_rlimit_as(const rlimit64& value) { + rlimit_as_ = value; + return *this; + } + Limits& set_rlimit_as(uint64_t value) { + rlimit_as_.rlim_cur = value; + rlimit_as_.rlim_max = value; + return *this; + } + const rlimit64& rlimit_cpu() const { return rlimit_cpu_; } + Limits& set_rlimit_cpu(const rlimit64& value) { + rlimit_cpu_ = value; + return *this; + } + Limits& set_rlimit_cpu(uint64_t value) { + rlimit_cpu_.rlim_cur = value; + rlimit_cpu_.rlim_max = value; + return *this; + } + const rlimit64& rlimit_fsize() const { return rlimit_fsize_; } + Limits& set_rlimit_fsize(const rlimit64& value) { + rlimit_fsize_ = value; + return *this; + } + Limits& set_rlimit_fsize(uint64_t value) { + rlimit_fsize_.rlim_cur = value; + rlimit_fsize_.rlim_max = value; + return *this; + } + const rlimit64& rlimit_nofile() const { return rlimit_nofile_; } + Limits& set_rlimit_nofile(const rlimit64& value) { + rlimit_nofile_ = value; + return *this; + } + Limits& set_rlimit_nofile(uint64_t value) { + rlimit_nofile_.rlim_cur = value; + rlimit_nofile_.rlim_max = value; + return *this; + } + const rlimit64& rlimit_core() const { return rlimit_core_; } + Limits& set_rlimit_core(const rlimit64& value) { + rlimit_core_ = value; + return *this; + } + Limits& set_rlimit_core(uint64_t value) { + rlimit_core_.rlim_cur = value; + rlimit_core_.rlim_max = value; + return *this; + } + + // Sets a wall time limit on an executor before running it. Set to + // absl::ZeroDuration() to disarm. The walltime limit is a timeout duration + // (e.g. 10 secs) not a deadline (e.g. 12:00). This can be useful in a simple + // scenario to set a wall limit before running the sandboxee, run the + // sandboxee, and expect it to finish within the limit. For an example, see + // examples/crc4. + Limits& set_walltime_limit(absl::Duration value) { + wall_time_limit_ = value; + return *this; + } + absl::Duration wall_time_limit() const { return wall_time_limit_; } + + private: + // Initial values for limits. Fields of rlimit64 are defined as __u64, + // so we use uint64_t here. + static constexpr uint64_t kIniRLimAS = RLIM64_INFINITY; + // 1024 seconds of real CPU time for each sandboxed process. + static constexpr uint64_t kIniRLimCPU = (1ULL << 10); + // 8GiB - Maximum size of individual files that can be created by each + // sandboxed process. + static constexpr uint64_t kIniRLimFSIZE = (8ULL << 30); + // 1024 file descriptors which can be used by each sandboxed process. + static constexpr uint64_t kIniRLimNOFILE = (1ULL << 10); + // No core files are allowed by default + static constexpr uint64_t kIniRLimCORE = (0); + // 120s - this is wall-time limit. Depending on the sandboxed load, this one, + // or the RLIMIT_CPU limit might be triggered faster + // cf. (https://en.wikipedia.org/wiki/Time_(Unix)#Real_time_vs_CPU_time) + static constexpr time_t kIniWallTimeLimit = (120ULL); + + // Address space size of a process, if big enough (say, above 512M), it's a + // crude representation of maximum RAM size used by the sandboxed process. + rlimit64 rlimit_as_; + // CPU time, might be triggered faster than the wall-time limit, if many + // threads are used. + rlimit64 rlimit_cpu_; + // Number of bytes which can be written to the FS by the process (just + // creating empty files is always allowed). + rlimit64 rlimit_fsize_; + // Number of NEW file descriptors which can be obtained by a process. 0 + // means that no new descriptors (files, sockets) can be created. + rlimit64 rlimit_nofile_; + // Size of a core file which is allowed to be created. Should be 0, unless + // you know what you are doing. + rlimit64 rlimit_core_; + // Getter for the client_limits_ structure. + + // Wall-time limit (local to Monitor). + absl::Duration wall_time_limit_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_LIMITS_H_ diff --git a/sandboxed_api/sandbox2/limits_test.cc b/sandboxed_api/sandbox2/limits_test.cc new file mode 100644 index 0000000..9b27b9c --- /dev/null +++ b/sandboxed_api/sandbox2/limits_test.cc @@ -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 "sandboxed_api/sandbox2/limits.h" + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "sandboxed_api/sandbox2/executor.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/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/util/status_matchers.h" + +namespace sandbox2 { +namespace { + +TEST(LimitsTest, RLimitASMmapUnderLimit) { + const std::string path = GetTestSourcePath("sandbox2/testcases/limits"); + std::vector args = {path, "1"}; // mmap(1 MiB) + auto executor = absl::make_unique(path, args); + executor->limits()->set_rlimit_as(100ULL << 20); // 100 MiB + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, sandbox2::PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + sandbox2::Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), sandbox2::Result::OK); + EXPECT_EQ(result.reason_code(), 0); +} + +TEST(LimitsTest, RLimitASMmapAboveLimit) { + const std::string path = GetTestSourcePath("sandbox2/testcases/limits"); + std::vector args = {path, "2"}; // mmap(100 MiB) + auto executor = absl::make_unique(path, args); + executor->limits()->set_rlimit_as(100ULL << 20); // 100 MiB + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, sandbox2::PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + sandbox2::Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), sandbox2::Result::OK); + EXPECT_EQ(result.reason_code(), 0); +} + +TEST(LimitsTest, RLimitASAllocaSmallUnderLimit) { + const std::string path = GetTestSourcePath("sandbox2/testcases/limits"); + std::vector args = {path, "3"}; // alloca(1 MiB) + auto executor = absl::make_unique(path, args); + executor->limits()->set_rlimit_as(100ULL << 20); // 100 MiB + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, sandbox2::PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + sandbox2::Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), sandbox2::Result::OK); + EXPECT_EQ(result.reason_code(), 0); +} + +TEST(LimitsTest, RLimitASAllocaBigUnderLimit) { + const std::string path = GetTestSourcePath("sandbox2/testcases/limits"); + std::vector args = {path, "4"}; // alloca(8 MiB) + auto executor = absl::make_unique(path, args); + executor->limits()->set_rlimit_as(100ULL << 20); // 100 MiB + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, sandbox2::PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + sandbox2::Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), sandbox2::Result::SIGNALED); + EXPECT_EQ(result.reason_code(), SIGSEGV); +} + +TEST(LimitsTest, RLimitASAllocaBigAboveLimit) { + const std::string path = GetTestSourcePath("sandbox2/testcases/limits"); + std::vector args = {path, "5"}; // alloca(100 MiB) + auto executor = absl::make_unique(path, args); + executor->limits()->set_rlimit_as(100ULL << 20); // 100 MiB + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, sandbox2::PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + sandbox2::Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), sandbox2::Result::SIGNALED); + EXPECT_EQ(result.reason_code(), SIGSEGV); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/logserver.cc b/sandboxed_api/sandbox2/logserver.cc new file mode 100644 index 0000000..8f2a612 --- /dev/null +++ b/sandboxed_api/sandbox2/logserver.cc @@ -0,0 +1,46 @@ +// 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/logserver.h" + +#include + +#include +#include "absl/memory/memory.h" +#include "sandboxed_api/sandbox2/logserver.pb.h" + +namespace sandbox2 { + +LogServer::LogServer(int fd) : comms_(absl::make_unique(fd)) {} + +void LogServer::Run() { + namespace logging = ::google; + LogMessage msg; + while (comms_->RecvProtoBuf(&msg)) { + logging::LogSeverity severity = msg.severity(); + const char* fatal_string = ""; + if (severity == logging::FATAL) { + // We don't want to trigger an abort() in the executor for FATAL logs. + severity = logging::ERROR; + fatal_string = " FATAL"; + } + logging::LogMessage log_message(msg.path().c_str(), msg.line(), severity); + log_message.stream() << "(sandboxee " << msg.pid() << fatal_string + << "): " << msg.message(); + } + + LOG(INFO) << "Receive failed, shutting down LogServer"; +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/logserver.h b/sandboxed_api/sandbox2/logserver.h new file mode 100644 index 0000000..e9b26f5 --- /dev/null +++ b/sandboxed_api/sandbox2/logserver.h @@ -0,0 +1,42 @@ +// 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_LOGSERVER_H_ +#define SANDBOXED_API_SANDBOX2_LOGSERVER_H_ + +#include + +#include "sandboxed_api/sandbox2/comms.h" + +namespace sandbox2 { + +// The LogServer waits for messages from the sandboxee on a given file +// descriptor and logs them using the standard base/logging facilities. +class LogServer { + public: + explicit LogServer(int fd); + + LogServer(const LogServer&) = delete; + LogServer& operator=(const LogServer&) = delete; + + // Starts handling incoming log messages. + void Run(); + + private: + std::unique_ptr comms_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_LOGSERVER_H_ diff --git a/sandboxed_api/sandbox2/logserver.proto b/sandboxed_api/sandbox2/logserver.proto new file mode 100644 index 0000000..7d4cb4a --- /dev/null +++ b/sandboxed_api/sandbox2/logserver.proto @@ -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 sandbox2; + +message LogMessage { + required int32 severity = 1; + required string path = 2; + required int32 line = 3; + required string message = 4; + required int32 pid = 5; +} diff --git a/sandboxed_api/sandbox2/logsink.cc b/sandboxed_api/sandbox2/logsink.cc new file mode 100644 index 0000000..ac0943e --- /dev/null +++ b/sandboxed_api/sandbox2/logsink.cc @@ -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. + +#include "sandboxed_api/sandbox2/logsink.h" + +#include + +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/synchronization/mutex.h" +#include "sandboxed_api/sandbox2/logserver.pb.h" + +namespace sandbox2 { + +constexpr char LogSink::kLogFDName[]; + +LogSink::LogSink(int fd) : comms_(fd) { AddLogSink(this); } + +LogSink::~LogSink() { RemoveLogSink(this); } + +void LogSink::send(google::LogSeverity severity, const char* full_filename, + const char* base_filename, int line, + const struct tm* tm_time, const char* message, + size_t message_len) { + absl::MutexLock l(&lock_); + + LogMessage msg; + msg.set_severity(static_cast(severity)); + msg.set_path(base_filename); + msg.set_line(line); + msg.set_message(absl::StrCat(absl::string_view{message, message_len}, "\n")); + msg.set_pid(getpid()); + + if (!comms_.SendProtoBuf(msg)) { + std::cerr << "sending log message to supervisor failed: " << std::endl + << msg.DebugString() << std::endl; + } + + if (severity == google::FATAL) { + // Raise a SIGABRT to prevent the remaining code in logging to try to dump a + // symbolized stack trace which can lead to syscall violations. + kill(0, SIGABRT); + } +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/logsink.h b/sandboxed_api/sandbox2/logsink.h new file mode 100644 index 0000000..f824198 --- /dev/null +++ b/sandboxed_api/sandbox2/logsink.h @@ -0,0 +1,49 @@ +// 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_LOGSINK_H_ +#define SANDBOXED_API_SANDBOX2_LOGSINK_H_ + +#include +#include "absl/synchronization/mutex.h" +#include "sandboxed_api/sandbox2/comms.h" + +namespace sandbox2 { + +// The LogSink will register itself with the logging facilities and forward all +// log messages to the executor on a given file descriptor. +class LogSink : public google::LogSink { + public: + static constexpr char kLogFDName[] = "sb2_logsink"; + + explicit LogSink(int fd); + ~LogSink() override; + + LogSink(const LogSink&) = delete; + LogSink& operator=(const LogSink&) = delete; + + void send(google::LogSeverity severity, const char* full_filename, + const char* base_filename, int line, const struct tm* tm_time, + const char* message, size_t message_len) override; + + private: + Comms comms_; + + // Needed to make the LogSink thread safe. + absl::Mutex lock_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_LOGSINK_H_ diff --git a/sandboxed_api/sandbox2/monitor.cc b/sandboxed_api/sandbox2/monitor.cc new file mode 100644 index 0000000..abd1de7 --- /dev/null +++ b/sandboxed_api/sandbox2/monitor.cc @@ -0,0 +1,1058 @@ +// 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::Monitor class. + +#include "sandboxed_api/sandbox2/monitor.h" + +#include // NOLINT: Needs to come before linux/ipc.h + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "sandboxed_api/util/flag.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/sandbox2/client.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/limits.h" +#include "sandboxed_api/sandbox2/mounts.h" +#include "sandboxed_api/sandbox2/namespace.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/regs.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/sanitizer.h" +#include "sandboxed_api/sandbox2/stack-trace.h" +#include "sandboxed_api/sandbox2/syscall.h" +#include "sandboxed_api/sandbox2/util.h" + +ABSL_FLAG(bool, sandbox2_report_on_sandboxee_signal, true, + "Report sandbox2 sandboxee deaths caused by signals"); + +ABSL_FLAG(bool, sandbox2_report_on_sandboxee_timeout, true, + "Report sandbox2 sandboxee timeouts"); + +ABSL_DECLARE_FLAG(bool, sandbox2_danger_danger_permit_all); +ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); + +namespace sandbox2 { + +namespace { + +// We could use the ProcMapsIterator, however we want the full file content. +std::string ReadProcMaps(pid_t pid) { + std::ifstream input(absl::StrCat("/proc/", pid, "/maps"), + std::ios_base::in | std::ios_base::binary); + std::ostringstream contents; + contents << input.rdbuf(); + return contents.str(); +} + +} // namespace + +Monitor::Monitor(Executor* executor, Policy* policy, Notify* notify) + : executor_(executor), + notify_(notify), + policy_(policy), + comms_(executor_->ipc()->comms()), + ipc_(executor_->ipc()), + setup_counter_(new absl::BlockingCounter(1)), + done_(false), + wait_for_execve_(executor->enable_sandboxing_pre_execve_) { + std::string path = absl::GetFlag(FLAGS_sandbox2_danger_danger_permit_all_and_log); + if (!path.empty()) { + log_file_ = std::fopen(path.c_str(), "a+"); + PCHECK(log_file_ != nullptr) << "Failed to open log file '" << path << "'"; + } +} + +Monitor::~Monitor() { + CleanUpTimer(); + if (log_file_) { + std::fclose(log_file_); + } +} + +void Monitor::Run() { + using DecrementCounter = decltype(setup_counter_); + std::unique_ptr> + decrement_count{&setup_counter_, [](DecrementCounter* counter) { + (*counter)->DecrementCount(); + }}; + + struct MonitorCleanup { + ~MonitorCleanup() { + getrusage(RUSAGE_THREAD, capture->result_.GetRUsageMonitor()); + capture->notify_->EventFinished(capture->result_); + capture->ipc_->InternalCleanupFdMap(); + absl::MutexLock lock(&capture->done_mutex_); + capture->done_.store(true, std::memory_order_release); + } + Monitor* capture; + } monitor_cleanup{this}; + + if (!InitSetupTimer()) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_TIMERS); + return; + } + + // It'd be costly to initialize the sigset_t for each sigtimedwait() + // invocation, so do it once per Monitor. + sigset_t sigtimedwait_sset; + if (!InitSetupSignals(&sigtimedwait_sset)) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_SIGNALS); + return; + } + + // Don't trace the child: it will allow to use 'strace -f' with the whole + // sandbox master/monitor, which ptrace_attach'es to the child. + int clone_flags = CLONE_UNTRACED; + + // Get PID of the sandboxee. + pid_t init_pid = 0; + pid_ = executor_->StartSubProcess(clone_flags, policy_->GetNamespace(), + policy_->GetCapabilities(), &init_pid); + + if (init_pid < 0) { + // TODO(hamacher): does this require additional handling here? + LOG(ERROR) << "Spawning init process failed"; + } else if (init_pid > 0) { + PCHECK(ptrace(PTRACE_SEIZE, init_pid, 0, PTRACE_O_EXITKILL) == 0); + } + + if (pid_ < 0) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_SUBPROCESS); + return; + } + + if (!notify_->EventStarted(pid_, comms_)) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_NOTIFY); + return; + } + if (!InitAcceptConnection()) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_CONNECTION); + return; + } + if (!InitSendIPC()) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_IPC); + return; + } + if (!InitSendCwd()) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_CWD); + return; + } + if (!InitSendPolicy()) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_POLICY); + return; + } + if (!WaitForSandboxReady()) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_WAIT); + return; + } + if (!InitApplyLimits()) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_LIMITS); + return; + } + // This call should be the last in the init sequence, because it can cause the + // sandboxee to enter ptrace-stopped state, in which it will not be able to + // send any messages over the Comms channel. + if (!InitPtraceAttach()) { + result_.SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_PTRACE); + return; + } + + // Tell the parent thread (Sandbox2 object) that we're done with the initial + // set-up process of the sandboxee. + decrement_count.reset(); + + MainLoop(&sigtimedwait_sset); + + // Disarm the timer: it will be deleted in ~Monitor, but the Monitor object + // lifetime is controlled by owner of Sandbox2, and we don't want to leave any + // timers behind (esp. armed ones) in the meantime. + TimerArm(absl::ZeroDuration()); +} + +bool Monitor::IsActivelyMonitoring() { + // If we're still waiting for execve(), then we allow all syscalls. + return !wait_for_execve_; +} + +void Monitor::SetActivelyMonitoring() { wait_for_execve_ = false; } + +void Monitor::MainSignals(int signo, siginfo_t* si) { + VLOG(3) << "Signal '" << strsignal(signo) << "' (" << signo + << ") received from PID: " << si->si_pid; + + // SIGCHLD is received frequently due to ptrace() events being sent by child + // processes; return early to avoid costly syscalls. + if (signo == SIGCHLD) { + return; + } + + // We should only receive signals from the same process (thread group). Other + // signals are suspicious (esp. if coming from a sandboxed process) Using + // syscall(__NR_getpid) here because getpid() is cached in glibc, and it + // might return previous pid if bare syscall(__NR_fork) was used instead of + // fork(). + // + // The notable exception are signals caused by timer_settime which are sent + // by the kernel. + if (signo != Monitor::kTimerWallTimeSignal && + si->si_pid != util::Syscall(__NR_getpid)) { + LOG(ERROR) << "Monitor received signal '" << strsignal(signo) << "' (" + << signo << ") from PID " << si->si_pid + << " which is not in the current thread group"; + return; + } + + switch (signo) { + case Monitor::kExternalKillSignal: + VLOG(1) << "Will kill the main pid"; + ActionProcessKill(pid_, Result::EXTERNAL_KILL, 0); + break; + case Monitor::kTimerWallTimeSignal: + VLOG(1) << "Sandbox process hit timeout due to the walltime timer"; + ActionProcessKill(pid_, Result::TIMEOUT, 0); + break; + case Monitor::kTimerSetSignal: + VLOG(1) << "Will set the walltime timer to " << si->si_value.sival_int + << " seconds"; + TimerArm(absl::Seconds(si->si_value.sival_int)); + break; + case Monitor::kDumpStackSignal: + VLOG(1) << "Dump the main pid's stack"; + should_dump_stack_ = true; + PidInterrupt(pid_); + break; + default: + LOG(ERROR) << "Unknown signal received: " << signo; + break; + } +} + +// Not defined in glibc. +#define __WPTRACEEVENT(x) ((x & 0xff0000) >> 16) +bool Monitor::MainWait() { + // All possible process status change event must be checked as SIGCHLD + // is reported once only for all events that arrived at the same time. + for (;;) { + int status; + // It should be a non-blocking operation (hence WNOHANG), so this function + // returns quickly if there are no events to be processed. + int ret = waitpid(-1, &status, __WNOTHREAD | __WALL | WUNTRACED | WNOHANG); + + // No traced processes have changed their status yet. + if (ret == 0) { + return false; + } + + if (ret == -1 && errno == ECHILD) { + LOG(ERROR) << "PANIC(). The main process has not exited yet, " + << "yet we haven't seen its exit event"; + // We'll simply exit which will kill all remaining processes (if + // there are any) because of the PTRACE_O_EXITKILL ptrace() flag. + return true; + } + if (ret == -1 && errno == EINTR) { + VLOG(3) << "waitpid() interruped with EINTR"; + continue; + } + if (ret == -1) { + PLOG(ERROR) << "waitpid() failed"; + continue; + } + + VLOG(3) << "waitpid() returned with PID: " << ret << ", status: " << status; + + if (WIFEXITED(status)) { + VLOG(1) << "PID: " << ret + << " finished with code: " << WEXITSTATUS(status); + // That's the main process, set the exit code, and exit. It will kill + // all remaining processes (if there are any) because of the + // PTRACE_O_EXITKILL ptrace() flag. + if (ret == pid_) { + if (IsActivelyMonitoring()) { + result_.SetExitStatusCode(Result::OK, WEXITSTATUS(status)); + } else { + result_.SetExitStatusCode(Result::SETUP_ERROR, + Result::FAILED_MONITOR); + } + return true; + } + } else if (WIFSIGNALED(status)) { + VLOG(1) << "PID: " << ret << " terminated with signal: " + << util::GetSignalName(WTERMSIG(status)); + if (ret == pid_) { + // That's the main process, depending on the result of the process take + // the register content and/or the stack trace. The death of this + // process will cause all remaining processes to be killed (if there are + // any), see the PTRACE_O_EXITKILL ptrace() flag. + + // When the process is killed from a signal from within the result + // status will be still unset, fix this. + // The other cases should either be already handled, or (in the case of + // Result::OK) should be impossible to reach. + if (result_.final_status() == Result::UNSET) { + result_.SetExitStatusCode(Result::SIGNALED, WTERMSIG(status)); + } else if (result_.final_status() == Result::OK) { + LOG(ERROR) << "Unexpected codepath taken"; + } + return true; + } + } else if (WIFSTOPPED(status)) { + VLOG(2) << "PID: " << ret + << " received signal: " << util::GetSignalName(WSTOPSIG(status)) + << " with event: " << __WPTRACEEVENT(status); + StateProcessStopped(ret, status); + } else if (WIFCONTINUED(status)) { + VLOG(2) << "PID: " << ret << " is being continued"; + } + } +} + +void Monitor::MainLoop(sigset_t* sset) { + for (;;) { + // Use a time-out, so we can check for missed waitpid() events. It should + // not happen during regular operations, so it's a defense-in-depth + // mechanism against SIGCHLD signals being lost by the kernel (since these + // are not-RT signals - i.e. not queued). + static const timespec ts = {kWakeUpPeriodSec, kWakeUpPeriodNSec}; + + // Wait for any kind of events, e.g. signals sent from the parent process, + // or SIGCHLD sent by kernel indicating that state of one of the traced + // processes has changed. + siginfo_t si; + int ret = sigtimedwait(sset, &si, &ts); + if (ret > 0) { + // Process signals which arrived. + MainSignals(ret, &si); + } + + // If CheckWait reported no more traced processes, or that + // the main pid had exited, we should break this loop (i.e. our job is + // done here). + // + // MainWait() should use a not-blocking (e.g. WNOHANG with waitpid()) + // syntax, so it returns quickly if there are not status changes in + // traced processes. + if (MainWait()) { + return; + } + } +} + +bool Monitor::InitSetupTimer() { + walltime_timer_ = absl::make_unique(); + + // Set the wall-time timer. + sigevent sevp; + sevp.sigev_value.sival_ptr = walltime_timer_.get(); + sevp.sigev_signo = kTimerWallTimeSignal; + sevp.sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL; + sevp._sigev_un._tid = static_cast(util::Syscall(__NR_gettid)); + // GLibc's implementation seem to mis-behave during timer_delete, as it's + // trying to find out whether POSIX TIMERs are available. So, we stick to + // syscalls for this class of calls. + if (util::Syscall(__NR_timer_create, CLOCK_REALTIME, + reinterpret_cast(&sevp), + reinterpret_cast(walltime_timer_.get())) == -1) { + walltime_timer_ = nullptr; + PLOG(ERROR) << "timer_create(CLOCK_REALTIME, walltime_timer_)"; + return false; + } + return TimerArm(executor_->limits()->wall_time_limit()); +} + +// Can be used from a signal handler. Avoid non-reentrant functions. +bool Monitor::TimerArm(absl::Duration duration) { + VLOG(2) << (duration == absl::ZeroDuration() ? "Disarming" : "Arming") + << " the walltime timer with " << absl::FormatDuration(duration); + + itimerspec ts; + absl::Duration rem; + ts.it_value.tv_sec = absl::IDivDuration(duration, absl::Seconds(1), &rem); + ts.it_value.tv_nsec = absl::ToInt64Nanoseconds(rem); + ts.it_interval.tv_sec = + duration != absl::ZeroDuration() ? 1L : 0L; // Re-fire every 1 sec. + ts.it_interval.tv_nsec = 0UL; + itimerspec* null_ts = nullptr; + if (util::Syscall(__NR_timer_settime, + reinterpret_cast(*walltime_timer_), 0, + reinterpret_cast(&ts), + reinterpret_cast(null_ts)) == -1) { + PLOG(ERROR) << "timer_settime(): time: " << absl::FormatDuration(duration); + return false; + } + + return true; +} + +void Monitor::CleanUpTimer() { + if (walltime_timer_) { + if (util::Syscall(__NR_timer_delete, + reinterpret_cast(*walltime_timer_)) == -1) { + PLOG(ERROR) << "timer_delete()"; + } + } +} + +bool Monitor::InitSetupSig(int signo, sigset_t* sset) { + // sigtimedwait will react (wake-up) to arrival of this signal. + sigaddset(sset, signo); + + // Block this specific signal, so only sigtimedwait reacts to it. + sigset_t block_set; + if (sigemptyset(&block_set) == -1) { + PLOG(ERROR) << "sigemptyset()"; + return false; + } + if (sigaddset(&block_set, signo) == -1) { + PLOG(ERROR) << "sigaddset(" << signo << ")"; + return false; + } + if (pthread_sigmask(SIG_BLOCK, &block_set, nullptr) == -1) { + PLOG(ERROR) << "pthread_sigmask(SIG_BLOCK, " << signo << ")"; + return false; + } + + return true; +} + +bool Monitor::InitSetupSignals(sigset_t* sset) { + sigemptyset(sset); + + return Monitor::InitSetupSig(kExternalKillSignal, sset) && + Monitor::InitSetupSig(kTimerWallTimeSignal, sset) && + Monitor::InitSetupSig(kTimerSetSignal, sset) && + Monitor::InitSetupSig(kDumpStackSignal, sset) && + // SIGCHLD means that a new children process status change event + // has been delivered (e.g. due ptrace notification). + Monitor::InitSetupSig(SIGCHLD, sset); +} + +bool Monitor::InitSendPolicy() { + if (!policy_->SendPolicy(comms_)) { + LOG(ERROR) << "Couldn't send policy"; + return false; + } + + return true; +} + +bool Monitor::InitSendCwd() { + if (!comms_->SendString(executor_->cwd_)) { + PLOG(ERROR) << "Couldn't send cwd"; + return false; + } + + return true; +} + +bool Monitor::InitApplyLimit(pid_t pid, __rlimit_resource resource, + const rlimit64& rlim) const { + std::string rlim_name = absl::StrCat("UNKNOWN: ", resource); + switch (resource) { + case RLIMIT_AS: + rlim_name = "RLIMIT_AS"; + break; + case RLIMIT_FSIZE: + rlim_name = "RLIMIT_FSIZE"; + break; + case RLIMIT_NOFILE: + rlim_name = "RLIMIT_NOFILE"; + break; + case RLIMIT_CPU: + rlim_name = "RLIMIT_CPU"; + break; + case RLIMIT_CORE: + rlim_name = "RLIMIT_CORE"; + break; + default: + break; + } + + rlimit64 curr_limit; + if (prlimit64(pid, resource, nullptr, &curr_limit) == -1) { + PLOG(ERROR) << "prlimit64(" << pid << ", " << rlim_name << ")"; + } else { + // In such case, don't update the limits, as it will fail. Just stick to the + // current ones (which are already lower than intended). + if (rlim.rlim_cur > curr_limit.rlim_max) { + LOG(ERROR) << rlim_name << ": new.current > current.max (" + << rlim.rlim_cur << " > " << curr_limit.rlim_max + << "), skipping"; + return true; + } + } + if (prlimit64(pid, resource, &rlim, nullptr) == -1) { + PLOG(ERROR) << "prlimit64(RLIMIT_AS, " << rlim.rlim_cur << ")"; + return false; + } + + return true; +} + +bool Monitor::InitApplyLimits() { + Limits* limits = executor_->limits(); + return InitApplyLimit(pid_, RLIMIT_AS, limits->rlimit_as()) && + InitApplyLimit(pid_, RLIMIT_CPU, limits->rlimit_cpu()) && + InitApplyLimit(pid_, RLIMIT_FSIZE, limits->rlimit_fsize()) && + InitApplyLimit(pid_, RLIMIT_NOFILE, limits->rlimit_nofile()) && + InitApplyLimit(pid_, RLIMIT_CORE, limits->rlimit_core()); +} + +bool Monitor::InitSendIPC() { return ipc_->SendFdsOverComms(); } + +bool Monitor::WaitForSandboxReady() { + uint32_t tmp; + if (!comms_->RecvUint32(&tmp)) { + LOG(ERROR) << "Couldn't receive 'Client::kClient2SandboxReady' message"; + return false; + } + if (tmp != Client::kClient2SandboxReady) { + LOG(ERROR) << "Received " << tmp << " != Client::kClient2SandboxReady (" + << Client::kClient2SandboxReady << ")"; + return false; + } + return true; +} + +bool Monitor::InitPtraceAttach() { + sanitizer::WaitForTsan(); + + // Get a list of tasks. + std::set tasks; + if (!sanitizer::GetListOfTasks(pid_, &tasks)) { + LOG(ERROR) << "Could not get list of tasks"; + return false; + } + + // With TSYNC, we can allow threads: seccomp applies to all threads. + + if (tasks.size() > 1) { + LOG(WARNING) << "PID " << pid_ << " has " << tasks.size() << " threads," + << " at the time of call to SandboxMeHere. If you are seeing" + << " more sandbox violations than expected, this might be" + << " the reason why" + << "."; + } + + intptr_t ptrace_opts = + PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | + PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | + PTRACE_O_TRACEEXIT | PTRACE_O_TRACESECCOMP | PTRACE_O_EXITKILL; + + bool main_pid_found = false; + for (auto task : tasks) { + if (task == pid_) { + main_pid_found = true; + } + + // In some situations we allow ptrace to try again when it fails. + bool ptrace_succeeded = false; + int retries = 0; + auto deadline = absl::Now() + absl::Seconds(2); + while (absl::Now() < deadline) { + int ret = ptrace(PTRACE_SEIZE, task, 0, ptrace_opts); + if (ret == 0) { + ptrace_succeeded = true; + break; + } + if (ret != 0 && errno == ESRCH) { + // A task may have exited since we captured the task list, we will allow + // things to continue after we log a warning. + PLOG(WARNING) << "ptrace(PTRACE_SEIZE, " << task << ", " + << absl::StrCat("0x", absl::Hex(ptrace_opts)) + << ") skipping exited task. Continuing with other tasks."; + ptrace_succeeded = true; + break; + } + if (ret != 0 && errno == EPERM) { + // Sometimes when a task is exiting we can get an EPERM from ptrace. + // Let's try again up until the timeout in this situation. + PLOG(WARNING) << "ptrace(PTRACE_SEIZE, " << task << ", " + << absl::StrCat("0x", absl::Hex(ptrace_opts)) + << "), trying again..."; + + // Exponential Backoff. + constexpr auto kInitialRetry = absl::Milliseconds(1); + constexpr auto kMaxRetry = absl::Milliseconds(20); + const auto retry_interval = + kInitialRetry * (1 << std::min(10, retries++)); + absl::SleepFor(std::min(retry_interval, kMaxRetry)); + continue; + } + + // Any other errno will be considered a failure. + PLOG(ERROR) << "ptrace(PTRACE_SEIZE, " << task << ", " + << absl::StrCat("0x", absl::Hex(ptrace_opts)) << ") failed."; + return false; + } + + if (!ptrace_succeeded) { + LOG(ERROR) << "ptrace(PTRACE_SEIZE, " << task << ", " + << absl::StrCat("0x", absl::Hex(ptrace_opts)) + << ") failed after retrying until the timeout."; + return false; + } + } + + if (!main_pid_found) { + LOG(ERROR) << "The pid " << pid_ << " was not found in its own tasklist."; + return false; + } + + // Get a list of tasks after attaching. + std::set tasks_after; + if (!sanitizer::GetListOfTasks(pid_, &tasks_after)) { + LOG(ERROR) << "Could not get list of tasks"; + return false; + } + + // Check that no new threads have shown up. Note: tasks_after can have fewer + // tasks than before but no new tasks can be added as they would be missing + // from the initial task list. + if (!std::includes(tasks.begin(), tasks.end(), tasks_after.begin(), + tasks_after.end())) { + LOG(ERROR) << "The pid " << pid_ + << " spawned new threads while we were trying to attach to it."; + return false; + } + + // No glibc wrapper for gettid - see 'man gettid'. + VLOG(1) << "Monitor (PID: " << getpid() + << ", TID: " << util::Syscall(__NR_gettid) + << ") attached to PID: " << pid_; + + // Technically, the sandboxee can be in a ptrace-stopped state right now, + // because some signal could have arrived in the meantime. Yet, this + // Comms::SendUint32 call shouldn't lock our process, because the underlying + // socketpair() channel is buffered, hence it will accept the uint32_t message + // no matter what is the current state of the sandboxee, and it will allow for + // our process to continue and unlock the sandboxee with the proper ptrace + // event handling. + if (!comms_->SendUint32(Client::kSandbox2ClientDone)) { + LOG(ERROR) << "Couldn't send Client::kSandbox2ClientDone message"; + return false; + } + return true; +} + +bool Monitor::InitAcceptConnection() { + // It's a pre-connected Comms channel, no need to accept new connection or + // verify the peer (sandboxee). + if (comms_->IsConnected()) { + return true; + } + + if (!comms_->Accept()) { + return false; + } + + // Check whether the PID which has connected to us, is the PID we're + // expecting. + pid_t cred_pid; + uid_t cred_uid; + gid_t cred_gid; + if (!comms_->RecvCreds(&cred_pid, &cred_uid, &cred_gid)) { + LOG(ERROR) << "Couldn't receive credentials"; + return false; + } + + if (pid_ != cred_pid) { + LOG(ERROR) << "Initial PID (" << pid_ << ") differs from the PID received " + << "from the peer (" << cred_pid << ")"; + return false; + } + + return true; +} + +void Monitor::ActionProcessContinue(pid_t pid, int signo) { + if (ptrace(PTRACE_CONT, pid, 0, signo) == -1) { + PLOG(ERROR) << "ptrace(PTRACE_CONT, pid=" << pid << ", sig=" << signo + << ")"; + } +} + +void Monitor::ActionProcessStop(pid_t pid, int signo) { + if (ptrace(PTRACE_LISTEN, pid, 0, signo) == -1) { + PLOG(ERROR) << "ptrace(PTRACE_LISTEN, pid=" << pid << ", sig=" << signo + << ")"; + } +} + +void Monitor::ActionProcessSyscall(Regs* regs, const Syscall& syscall) { + // If the sandboxing is not enabled yet, allow the first __NR_execveat. + if (syscall.nr() == __NR_execveat && !IsActivelyMonitoring()) { + VLOG(1) << "[PERMITTED/BEFORE_EXECVEAT]: " + << "SYSCALL ::: PID: " << regs->pid() << ", PROG: '" + << util::GetProgName(regs->pid()) + << "' : " << syscall.GetDescription(); + ActionProcessContinue(regs->pid(), 0); + return; + } + + // Notify can decide whether we want to allow this syscall. It could be useful + // for sandbox setups in which some syscalls might still need some logging, + // but nonetheless be allowed ('permissible syscalls' in sandbox v1). + if (notify_->EventSyscallTrap(syscall)) { + LOG(WARNING) << "[PERMITTED]: SYSCALL ::: PID: " << regs->pid() + << ", PROG: '" << util::GetProgName(regs->pid()) + << "' : " << syscall.GetDescription(); + + ActionProcessContinue(regs->pid(), 0); + return; + } + + // TODO(wiktorg): Further clean that up, probably while doing monitor cleanup + // log_file_ not null iff FLAGS_sandbox2_danger_danger_permit_all_and_log is + // set. + if (log_file_) { + std::string syscall_description = syscall.GetDescription(); + PCHECK(absl::FPrintF(log_file_, "PID: %d %s\n", regs->pid(), + syscall_description) >= 0); + ActionProcessContinue(regs->pid(), 0); + return; + } + + if (absl::GetFlag(FLAGS_sandbox2_danger_danger_permit_all)) { + ActionProcessContinue(regs->pid(), 0); + return; + } + + ActionProcessSyscallViolation(regs, syscall, kSyscallViolation); +} + +void Monitor::ActionProcessSyscallViolation(Regs* regs, const Syscall& syscall, + ViolationType violation_type) { + pid_t pid = regs->pid(); + + LogAccessViolation(syscall); + notify_->EventSyscallViolation(syscall, violation_type); + result_.SetExitStatusCode(Result::VIOLATION, syscall.nr()); + result_.SetSyscall(absl::make_unique(syscall)); + // Only get the stacktrace if we are not in the libunwind sandbox (avoid + // recursion). + if (executor_->libunwind_sbox_for_pid_ == 0 && policy_->GetNamespace()) { + if (policy_->collect_stacktrace_on_violation_) { + result_.SetStackTrace( + GetStackTrace(regs, policy_->GetNamespace()->mounts())); + LOG(ERROR) << "Stack trace: " << result_.GetStackTrace(); + } else { + LOG(ERROR) << "Stack traces have been disabled"; + } + } + // We make the result object create its own Reg instance. our regs is a + // pointer to a stack variable which might not live long enough. + result_.LoadRegs(pid); + result_.SetProgName(util::GetProgName(pid)); + result_.SetProcMaps(ReadProcMaps(pid_)); + + // Rewrite the syscall argument to something invalid (-1). The process will + // be killed by ActionProcessKill(), so this is just a precaution. + auto status = regs->SkipSyscallReturnValue(-ENOSYS); + if (!status.ok()) { + LOG(ERROR) << status; + } + + ActionProcessKill(pid, Result::VIOLATION, syscall.nr()); +} + +void Monitor::LogAccessViolation(const Syscall& syscall) { + // Do not unwind libunwind. + if (executor_->libunwind_sbox_for_pid_ != 0) { + LOG(ERROR) << "Sandbox violation during execution of libunwind: " + << syscall.GetDescription(); + return; + } + + uintptr_t syscall_nr = syscall.nr(); + uintptr_t arg0 = syscall.args()[0]; + + // So, this is an invalid syscall. Will be killed by seccomp-bpf policies as + // well, but we should be on a safe side here as well. + LOG(ERROR) << "SANDBOX VIOLATION : PID: " << syscall.pid() << ", PROG: '" + << util::GetProgName(syscall.pid()) + << "' : " << syscall.GetDescription(); + + // This follows policy in Policy::GetDefaultPolicy - keep it in sync. + if (syscall.arch() != Syscall::GetHostArch()) { + LOG(ERROR) + << "This is a violation because the syscall was issued because the" + << " sandboxee and executor architectures are different."; + return; + } + + if (syscall_nr == __NR_ptrace) { + LOG(ERROR) + << "This is a violation because the ptrace syscall would be unsafe in" + << " sandbox2, so it has been blocked."; + return; + } + if (syscall_nr == __NR_bpf) { + LOG(ERROR) + << "This is a violation because the bpf syscall would be risky in" + << " a sandbox, so it has been blocked."; + return; + } + + if (syscall_nr == __NR_clone && ((arg0 & CLONE_UNTRACED) != 0)) { + LOG(ERROR) << "This is a violation because calling clone with CLONE_UNTRACE" + << " would be unsafe in sandbox2, so it has been blocked."; + return; + } +} + +void Monitor::ActionProcessKill(pid_t pid, Result::StatusEnum status, + uintptr_t code) { + // Avoid overwriting result if we set it for instance after a violation. + if (result_.final_status() == Result::UNSET) { + result_.SetExitStatusCode(status, code); + } + + VLOG(1) << "Sending SIGKILL to the PID: " << pid_; + if (kill(pid_, SIGKILL) != 0) { + LOG(FATAL) << "Could not send SIGKILL to PID " << pid_; + } +} + +void Monitor::EventPtraceSeccomp(pid_t pid, int event_msg) { + VLOG(1) << "PID: " << pid << " violation uncovered via the SECCOMP_EVENT"; + // If the seccomp-policy is using RET_TRACE, we request that it returns the + // syscall architecture identifier in the SECCOMP_RET_DATA. + const auto syscall_arch = static_cast(event_msg); + Regs regs(pid); + auto status = regs.Fetch(); + if (!status.ok()) { + LOG(ERROR) << status; + ActionProcessKill(pid, Result::INTERNAL_ERROR, Result::FAILED_FETCH); + return; + } + + Syscall syscall = regs.ToSyscall(syscall_arch); + // If the architecture of the syscall used is different that the current host + // architecture, report a violation. + if (syscall_arch != Syscall::GetHostArch()) { + ActionProcessSyscallViolation(®s, syscall, kArchitectureSwitchViolation); + return; + } + + ActionProcessSyscall(®s, syscall); +} + +void Monitor::EventPtraceExec(pid_t pid, int event_msg) { + if (!IsActivelyMonitoring()) { + VLOG(1) << "PTRACE_EVENT_EXEC seen from PID: " << event_msg + << ". SANDBOX ENABLED!"; + SetActivelyMonitoring(); + } + ActionProcessContinue(pid, 0); +} + +void Monitor::EventPtraceExit(pid_t pid, int event_msg) { + // A regular exit, let it continue. + if (WIFEXITED(event_msg)) { + ActionProcessContinue(pid, 0); + return; + } + + // Everything except the SECCOMP violation can continue. + if (!WIFSIGNALED(event_msg) || WTERMSIG(event_msg) != SIGSYS) { + // Process is dying because it received a signal. + // This can occur in three cases: + // 1) Process was killed from the sandbox, in this case the result status + // was already set to Result::EXTERNAL_KILL. We do not get the stack + // trace in this case. + // 2) Process was killed because it hit a timeout. The result status is + // also already set, however we are interested in the stack trace. + // 3) Regular signal. We need to obtain everything. The status will be set + // upon the process exit handler. + if (pid == pid_) { + result_.LoadRegs(pid_); + result_.SetProgName(util::GetProgName(pid_)); + result_.SetProcMaps(ReadProcMaps(pid_)); + bool stacktrace_collection_possible = + policy_->GetNamespace() && executor_->libunwind_sbox_for_pid_ == 0; + auto collect_stacktrace = [this]() { + result_.SetStackTrace(GetStackTrace(result_.GetRegs(), + policy_->GetNamespace()->mounts())); + }; + switch (result_.final_status()) { + case Result::EXTERNAL_KILL: + if (stacktrace_collection_possible && + policy_->collect_stacktrace_on_kill_) { + collect_stacktrace(); + } + break; + case Result::TIMEOUT: + if (stacktrace_collection_possible && + policy_->collect_stacktrace_on_timeout_) { + collect_stacktrace(); + } + break; + case Result::VIOLATION: + break; + case Result::UNSET: + // Regular signal. + if (stacktrace_collection_possible && + policy_->collect_stacktrace_on_signal_) { + collect_stacktrace(); + } + break; + default: + LOG(ERROR) << "Unexpected codepath taken"; + break; + } + } + + ActionProcessContinue(pid, 0); + return; + } + + VLOG(1) << "PID: " << pid << " violation uncovered via the EXIT_EVENT"; + + // We do not generate the stack trace in the SECCOMP case as it will be + // generated during ActionProcessSyscallViolation anyway. + Regs regs(pid); + auto status = regs.Fetch(); + if (!status.ok()) { + LOG(ERROR) << status; + ActionProcessKill(pid, Result::INTERNAL_ERROR, Result::FAILED_FETCH); + return; + } + + auto syscall = regs.ToSyscall(Syscall::GetHostArch()); + + ActionProcessSyscallViolation(®s, syscall, kSyscallViolation); +} + +void Monitor::EventPtraceStop(pid_t pid, int stopsig) { + // It's not a real stop signal. For example PTRACE_O_TRACECLONE and similar + // flags to ptrace(PTRACE_SEIZE) might generate this event with SIGTRAP. + if (stopsig != SIGSTOP && stopsig != SIGTSTP && stopsig != SIGTTIN && + stopsig != SIGTTOU) { + ActionProcessContinue(pid, 0); + return; + } + // It's our PID stop signal. Stop it. + VLOG(2) << "PID: " << pid << " stopped due to " + << util::GetSignalName(stopsig); + ActionProcessStop(pid, 0); +} + +void Monitor::StateProcessStopped(pid_t pid, int status) { + int stopsig = WSTOPSIG(status); + if (__WPTRACEEVENT(status) == 0) { + // Must be a regular signal delivery. + VLOG(2) << "PID: " << pid + << " received signal: " << util::GetSignalName(stopsig); + notify_->EventSignal(pid, stopsig); + ActionProcessContinue(pid, stopsig); + return; + } + + unsigned long event_msg; // NOLINT + if (ptrace(PTRACE_GETEVENTMSG, pid, 0, &event_msg) == -1) { + if (errno == ESRCH) { + // This happens from time to time, the kernel does not guarantee us that + // we get the event in time. + PLOG(INFO) << "ptrace(PTRACE_GETEVENTMSG, " << pid << ")"; + return; + } + PLOG(ERROR) << "ptrace(PTRACE_GETEVENTMSG, " << pid << ")"; + ActionProcessKill(pid, Result::INTERNAL_ERROR, Result::FAILED_GETEVENT); + return; + } + + if (pid == pid_ && should_dump_stack_ && + executor_->libunwind_sbox_for_pid_ == 0 && policy_->GetNamespace()) { + Regs regs(pid); + auto status = regs.Fetch(); + if (status.ok()) { + VLOG(0) << "SANDBOX STACK : PID: " << pid << ", [" + << GetStackTrace(®s, policy_->GetNamespace()->mounts()) << "]"; + } else { + LOG(WARNING) << "FAILED TO GET SANDBOX STACK : " << status; + } + should_dump_stack_ = false; + } +#if !defined(PTRACE_EVENT_STOP) +#define PTRACE_EVENT_STOP 128 +#endif + + switch (__WPTRACEEVENT(status)) { + case PTRACE_EVENT_FORK: + /* fall through */ + case PTRACE_EVENT_VFORK: + /* fall through */ + case PTRACE_EVENT_CLONE: + /* fall through */ + case PTRACE_EVENT_VFORK_DONE: + ActionProcessContinue(pid, 0); + break; + case PTRACE_EVENT_EXEC: + VLOG(2) << "PID: " << pid << " PTRACE_EVENT_EXEC, PID: " << event_msg; + EventPtraceExec(pid, event_msg); + break; + case PTRACE_EVENT_EXIT: + VLOG(2) << "PID: " << pid << " PTRACE_EVENT_EXIT: " << event_msg; + EventPtraceExit(pid, event_msg); + break; + case PTRACE_EVENT_STOP: + VLOG(2) << "PID: " << pid << " PTRACE_EVENT_STOP: " << event_msg; + EventPtraceStop(pid, stopsig); + break; + case PTRACE_EVENT_SECCOMP: + VLOG(2) << "PID: " << pid << " PTRACE_EVENT_SECCOMP: " << event_msg; + EventPtraceSeccomp(pid, event_msg); + break; + default: + LOG(ERROR) << "Unknown ptrace event: " << __WPTRACEEVENT(status) + << " with data: " << event_msg; + break; + } +} + +void Monitor::PidInterrupt(pid_t pid) { + if (ptrace(PTRACE_INTERRUPT, pid, 0, 0) == -1) { + PLOG(WARNING) << "ptrace(PTRACE_INTERRUPT, pid=" << pid << ")"; + } +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/monitor.h b/sandboxed_api/sandbox2/monitor.h new file mode 100644 index 0000000..f3bfcdd --- /dev/null +++ b/sandboxed_api/sandbox2/monitor.h @@ -0,0 +1,219 @@ +// 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::Monitor class is responsible for tracking the processes, and +// displaying their current statuses (syscalls, states, violations). + +#ifndef SANDBOXED_API_SANDBOX2_MONITOR_H_ +#define SANDBOXED_API_SANDBOX2_MONITOR_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#include "absl/synchronization/blocking_counter.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/ipc.h" +#include "sandboxed_api/sandbox2/notify.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/regs.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/syscall.h" + +namespace sandbox2 { + +class Monitor final { + public: + // executor, policy and notify are not owned by the Monitor + Monitor(Executor* executor, Policy* policy, Notify* notify); + + Monitor(const Monitor&) = delete; + Monitor& operator=(const Monitor&) = delete; + + ~Monitor(); + + private: + friend class Sandbox2; + + // As per 'man 7 pthreads' pthreads uses first three RT signals, so we use + // something safe here (but still lower than __SIGRTMAX). + // + // A signal which makes wait() to exit due to interrupt, so the Monitor can + // check whether it should terminate. + static const int kExternalKillSignal = (__SIGRTMIN + 10); + // A signal which system timer delivers in case the wall-time timer limit was + // reached. + static const int kTimerWallTimeSignal = (__SIGRTMIN + 12); + // A signal which makes Monitor to arm its wall-time timer. + static const int kTimerSetSignal = (__SIGRTMIN + 13); + // Dump the main sandboxed process's stack trace to log. + static const int kDumpStackSignal = (__SIGRTMIN + 14); +#if ((__SIGRTMIN + 14) > __SIGRTMAX) +#error "sandbox2::Monitor exceeding > __SIGRTMAX)" +#endif + + // Timeout used with sigtimedwait (0.5s). + static const int kWakeUpPeriodSec = 0L; + static const int kWakeUpPeriodNSec = (500L * 1000L * 1000L); + + // Starts the Monitor. + void Run(); + + // Getters for private fields. + bool IsDone() const { return done_.load(std::memory_order_acquire); } + + // Getter/Setter for wait_for_execve_. + bool IsActivelyMonitoring(); + void SetActivelyMonitoring(); + + // Waits for events from monitored clients and signals from the main process. + void MainLoop(sigset_t* sset); + + // Analyzes signals which Monitor might have already received. + void MainSignals(int signo, siginfo_t* si); + + // Analyzes any possible children process status changes; returns 'true' if + // there are no more processes to track. + bool MainWait(); + + // Sends Policy to the Client. + // Returns success/failure status. + bool InitSendPolicy(); + + // Waits for the SandboxReady signal from the client. + // Returns success/failure status. + bool WaitForSandboxReady(); + + // ptrace(PTRACE_SEIZE) to the Client. + // Returns success/failure status. + bool InitPtraceAttach(); + + // Waits for the Client to connect. + // Returns success/failure status. + bool InitAcceptConnection(); + + // Sets up required signal masks/handlers; prepare mask for sigtimedwait(). + bool InitSetupSignals(sigset_t* sset); + + // Sets up a given signal; modify the sigmask used with sigtimedwait(). + bool InitSetupSig(int signo, sigset_t* sset); + + // Sends information about data exchange channels. + bool InitSendIPC(); + + // Sends information about the current working directory. + bool InitSendCwd(); + + // Applies limits on the sandboxee. + bool InitApplyLimits(); + + // Applies individual limit on the sandboxee. + bool InitApplyLimit(pid_t pid, __rlimit_resource resource, + const rlimit64& rlim) const; + + // Creates timers. + bool InitSetupTimer(); + + // Deletes timers. + void CleanUpTimer(); + + // Arms the walltime timer, absl::ZeroDuration() disarms the timer. + bool TimerArm(absl::Duration duration); + + // Final action with regard to PID. + // Continues PID with an optional signal. + void ActionProcessContinue(pid_t pid, int signo); + + // Stops the PID with an optional signal. + void ActionProcessStop(pid_t pid, int signo); + + // Logs the syscall violation and kills the process afterwards. + void ActionProcessSyscallViolation(Regs* regs, const Syscall& syscall, + ViolationType violation_type); + + // Prints a SANDBOX VIOLATION message based on the registers. + // If the registers match something disallowed by Policy::GetDefaultPolicy, + // then it also prints a additional description of the reason. + void LogAccessViolation(const Syscall& syscall); + + // PID called a syscall, or was killed due to syscall. + void ActionProcessSyscall(Regs* regs, const Syscall& syscall); + + // Kills the PID with PTRACE_KILL. + void ActionProcessKill(pid_t pid, Result::StatusEnum status, uintptr_t code); + + // Ptrace events: + // Syscall violation processing path. + void EventPtraceSeccomp(pid_t pid, int event_msg); + + // Processes exit path. + void EventPtraceExit(pid_t pid, int event_msg); + + // Processes excution path. + void EventPtraceExec(pid_t pid, int event_msg); + + // Processes stop path. + void EventPtraceStop(pid_t pid, int stopsig); + + // Changes the state of a given PID: + // Process is in a stopped state. + void StateProcessStopped(pid_t pid, int status); + + // Helpers operating on PIDs. + // Interrupts the PID. + void PidInterrupt(pid_t pid); + + // Internal objects, owned by the Sandbox2 object. + Executor* executor_; + Notify* notify_; + Policy* policy_; + Result result_; + // Comms channel ptr, copied from the Executor object for convenience. + Comms* comms_; + // IPC ptr, used for exchanging data with the sandboxee. + IPC* ipc_; + + // Parent (the Sandbox2 object) waits on it, until we either enable + // monitoring of a process (sandboxee) successfully, or the setup process + // fails. + std::unique_ptr setup_counter_; + // The Wall-Time timer for traced processes. + std::unique_ptr walltime_timer_; + + // The main tracked PID. + pid_t pid_ = -1; + + // The field indicates whether the sandboxing task has been completed (either + // successfully or with error). + std::atomic done_; + absl::Mutex done_mutex_; + // Should we dump the main sandboxed PID's stack? + bool should_dump_stack_ = false; + + // Is the sandboxee actively monitored, or maybe we're waiting for execve()? + bool wait_for_execve_; + // Log file specified by + // --sandbox_danger_danger_permit_all_and_log flag. + FILE* log_file_ = nullptr; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_MONITOR_H_ diff --git a/sandboxed_api/sandbox2/mounts.cc b/sandboxed_api/sandbox2/mounts.cc new file mode 100644 index 0000000..bb23290 --- /dev/null +++ b/sandboxed_api/sandbox2/mounts.cc @@ -0,0 +1,524 @@ +// 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/mounts.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "google/protobuf/util/message_differencer.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/ascii.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/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/minielf.h" +#include "sandboxed_api/sandbox2/util/path.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/raw_logging.h" +#include "sandboxed_api/util/canonical_errors.h" +#include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" + +namespace sandbox2 { +namespace { + +bool PathContainsNullByte(absl::string_view path) { + return path.find('\x00') != absl::string_view::npos; +} + +bool IsSameFile(const std::string& path1, const std::string& path2) { + struct stat stat1, stat2; + if (stat(path1.c_str(), &stat1) == -1) { + return false; + } + + if (stat(path2.c_str(), &stat2) == -1) { + return false; + } + + return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino; +} + +bool IsEquivalentNode(const sandbox2::MountTree::Node& n1, + const sandbox2::MountTree::Node& n2) { + // Node equals 1:1 + if (google::protobuf::util::MessageDifferencer::Equals(n1, n2)) { + return true; + } + + if (n1.node_case() != n2.node_case()) { + return false; + } + + // Check whether files/dirs are the same (e.g symlinks / hardlinks) + switch (n1.node_case()) { + case sandbox2::MountTree::Node::kFileNode: + return n1.file_node().is_ro() == n2.file_node().is_ro() && + IsSameFile(n1.file_node().outside(), n2.file_node().outside()); + case sandbox2::MountTree::Node::kDirNode: + return n1.dir_node().is_ro() == n2.dir_node().is_ro() && + IsSameFile(n1.dir_node().outside(), n2.dir_node().outside()); + default: + return false; + } +} + +absl::string_view GetOutsidePath(const MountTree::Node& node) { + switch (node.node_case()) { + case MountTree::Node::kFileNode: + return node.file_node().outside(); + case MountTree::Node::kDirNode: + return node.dir_node().outside(); + default: + SAPI_RAW_LOG(FATAL, "Invalid node type"); + return ""; // NOT REACHED + } +} + +::sapi::StatusOr ExistingPathInsideDir( + absl::string_view dir_path, absl::string_view relative_path) { + auto path = file::CleanPath(file::JoinPath(dir_path, relative_path)); + if (file_util::fileops::StripBasename(path) != dir_path) { + return ::sapi::InvalidArgumentError( + "Relative path goes above the base dir"); + } + if (!file_util::fileops::Exists(path, false)) { + return ::sapi::NotFoundError(absl::StrCat("Does not exist: ", path)); + } + return path; +} + +sapi::Status ValidateInterpreter(absl::string_view interpreter) { + const absl::flat_hash_set allowed_interpreters = { + "/lib64/ld-linux-x86-64.so.2", + }; + + if (!allowed_interpreters.contains(interpreter)) { + return ::sapi::InvalidArgumentError( + absl::StrCat("Interpreter not on the whitelist: ", interpreter)); + } + return ::sapi::OkStatus(); +} + +std::string ResolveLibraryPath(absl::string_view lib_name, + const std::vector& search_paths) { + for (const auto& search_path : search_paths) { + auto path_or = ExistingPathInsideDir(search_path, lib_name); + if (path_or.ok()) { + return path_or.ValueOrDie(); + } + } + return ""; +} + +std::string GetPlatform(absl::string_view interpreter) { +#if defined(__x86_64__) + constexpr absl::string_view kCpuPlatform = "x86_64"; +#elif defined(__powerpc64__) + constexpr absl::string_view kCpuPlatform = "ppc64"; +#else + constexpr absl::string_view kCpuPlatform = "unknown"; +#endif + return absl::StrCat(kCpuPlatform, "-linux-gnu"); +} + +} // namespace + +::sapi::Status Mounts::Insert(absl::string_view path, + const MountTree::Node& new_node) { + // Some sandboxes allow the inside/outside paths to be partially + // user-controlled with some sanitization. + // Since we're handling C++ strings and later convert them to C style + // strings, a null byte in a path component might silently truncate the path + // and mount something not expected by the caller. Check for null bytes in the + // strings to protect against this. + if (PathContainsNullByte(path)) { + return ::sapi::InvalidArgumentError( + absl::StrCat("Inside path contains a null byte: ", path)); + } + switch (new_node.node_case()) { + case MountTree::Node::kFileNode: + case MountTree::Node::kDirNode: { + auto outside_path = GetOutsidePath(new_node); + if (outside_path.empty()) { + return ::sapi::InvalidArgumentError("Outside path cannot be empty"); + } + if (PathContainsNullByte(outside_path)) { + return ::sapi::InvalidArgumentError( + absl::StrCat("Outside path contains a null byte: ", outside_path)); + } + break; + } + case MountTree::Node::kTmpfsNode: + case MountTree::Node::NODE_NOT_SET: + break; + } + + std::string fixed_path = file::CleanPath(path); + + if (!absl::StartsWith(fixed_path, "/")) { + return ::sapi::InvalidArgumentError("Only absolute paths are supported"); + } + + if (fixed_path == "/") { + return ::sapi::InvalidArgumentError("The root already exists"); + } + + std::vector parts; + + auto split = file::SplitPath(fixed_path); + absl::string_view cur = split.first; + std::string final_part = std::string(split.second); + + while (cur != "/") { + auto split = file::SplitPath(cur); + cur = split.first; + parts.push_back(split.second); + } + + MountTree* curtree = &mount_tree_; + for (auto part = parts.rbegin(); part != parts.rend(); ++part) { + curtree = &(curtree->mutable_entries() + ->insert({std::string(*part), MountTree()}) + .first->second); + if (curtree->has_node() && curtree->node().has_file_node()) { + return ::sapi::FailedPreconditionError( + absl::StrCat("Cannot insert ", path, + " since a file is mounted as a parent directory")); + } + } + + curtree = &(curtree->mutable_entries() + ->insert({final_part, MountTree()}) + .first->second); + + if (curtree->has_node()) { + if (IsEquivalentNode(curtree->node(), new_node)) { + SAPI_RAW_LOG(INFO, "Inserting %s with the same value twice", path); + return ::sapi::OkStatus(); + } + return ::sapi::FailedPreconditionError(absl::StrCat( + "Inserting ", path, " twice with conflicting values ", + curtree->node().DebugString(), " vs. ", new_node.DebugString())); + } + + if (new_node.has_file_node() && !curtree->entries().empty()) { + return ::sapi::FailedPreconditionError( + absl::StrCat("Trying to mount file over existing directory at ", path)); + } + + *curtree->mutable_node() = new_node; + return ::sapi::OkStatus(); +} + +::sapi::Status Mounts::AddFile(absl::string_view path, bool is_ro) { + return AddFileAt(path, path, is_ro); +} + +::sapi::Status Mounts::AddFileAt(absl::string_view outside, + absl::string_view inside, bool is_ro) { + MountTree::Node node; + auto* file_node = node.mutable_file_node(); + file_node->set_outside(std::string(outside)); + file_node->set_is_ro(is_ro); + return Insert(inside, node); +} + +::sapi::Status Mounts::AddDirectoryAt(absl::string_view outside, + absl::string_view inside, bool is_ro) { + MountTree::Node node; + auto dir_node = node.mutable_dir_node(); + dir_node->set_outside(std::string(outside)); + dir_node->set_is_ro(is_ro); + return Insert(inside, node); +} + +::sapi::Status Mounts::AddMappingsForBinary(const std::string& path, + absl::string_view ld_library_path) { + auto elf_or = ElfFile::ParseFromFile( + path, ElfFile::kGetInterpreter | ElfFile::kLoadImportedLibraries); + if (!elf_or.ok()) { + return ::sapi::FailedPreconditionError( + absl::StrCat("Could not parse ELF file: ", elf_or.status().message())); + } + auto elf = elf_or.ValueOrDie(); + const std::string interpreter = elf.interpreter(); + + if (interpreter.empty()) { + SAPI_RAW_VLOG(1, "The file %s is not a dynamic executable", path); + return ::sapi::OkStatus(); + } + + SAPI_RAW_VLOG(1, "The file %s is using interpreter %s", path, interpreter); + + std::vector search_paths; + // 1. LD_LIBRARY_PRELOAD + if (!ld_library_path.empty()) { + std::vector ld_library_paths = + absl::StrSplit(ld_library_path, absl::ByAnyChar(":;")); + search_paths.insert(search_paths.end(), ld_library_paths.begin(), + ld_library_paths.end()); + } + // 2. Standard paths + search_paths.insert(search_paths.end(), { + "/lib", + "/lib64", + "/usr/lib", + "/usr/lib64", + }); + SAPI_RETURN_IF_ERROR(ValidateInterpreter(interpreter)); + std::vector hw_cap_paths = { + GetPlatform(interpreter), + "tls", + }; + std::vector full_search_paths; + for (const auto& search_path : search_paths) { + for (int hw_caps_set = (1 << hw_cap_paths.size()) - 1; hw_caps_set >= 0; + --hw_caps_set) { + std::string path = search_path; + for (int hw_cap = 0; hw_cap < hw_cap_paths.size(); ++hw_cap) { + if ((hw_caps_set & (1 << hw_cap)) != 0) { + path = file::JoinPath(path, hw_cap_paths[hw_cap]); + } + } + if (file_util::fileops::Exists(path, /*fully_resolve=*/false)) { + full_search_paths.push_back(path); + } + } + } + + // Arbitrary cut-off values, so we can safely resolve the libs. + constexpr int kMaxWorkQueueSize = 1000; + constexpr int kMaxResolvingDepth = 10; + constexpr int kMaxResolvedEntries = 1000; + constexpr int kMaxLoadedEntries = 100; + constexpr int kMaxImportedLibraries = 100; + + absl::flat_hash_set imported_libraries; + std::vector> to_resolve; + { + auto imported_libs = elf.imported_libraries(); + if (imported_libs.size() > kMaxWorkQueueSize) { + return ::sapi::FailedPreconditionError( + "Exceeded max entries pending resolving limit"); + } + for (const auto& imported_lib : imported_libs) { + to_resolve.emplace_back(imported_lib, 1); + } + } + // This is DFS with an auxiliary stack + int resolved = 0; + int loaded = 0; + while (!to_resolve.empty()) { + int depth; + std::string lib; + std::tie(lib, depth) = to_resolve.back(); + to_resolve.pop_back(); + ++resolved; + if (resolved > kMaxResolvedEntries) { + return ::sapi::FailedPreconditionError( + "Exceeded max resolved entries limit"); + } + if (depth > kMaxResolvingDepth) { + return ::sapi::FailedPreconditionError( + "Exceeded max resolving depth limit"); + } + std::string resolved_lib = ResolveLibraryPath(lib, full_search_paths); + if (resolved_lib.empty()) { + continue; + } + if (imported_libraries.contains(resolved_lib)) { + continue; + } + imported_libraries.insert(resolved_lib); + if (imported_libraries.size() > kMaxImportedLibraries) { + return ::sapi::FailedPreconditionError( + "Exceeded max imported libraries limit"); + } + ++loaded; + if (loaded > kMaxLoadedEntries) { + return ::sapi::FailedPreconditionError( + "Exceeded max loaded entries limit"); + } + SAPI_ASSIGN_OR_RETURN( + auto lib_elf, + ElfFile::ParseFromFile(resolved_lib, ElfFile::kLoadImportedLibraries)); + auto imported_libs = lib_elf.imported_libraries(); + if (imported_libs.size() > kMaxWorkQueueSize - to_resolve.size()) { + return ::sapi::FailedPreconditionError( + "Exceeded max entries pending resolving limit"); + } + for (const auto& imported_lib : imported_libs) { + to_resolve.emplace_back(imported_lib, depth + 1); + } + } + + SAPI_RETURN_IF_ERROR(AddFile(interpreter)); + for (const auto& lib : imported_libraries) { + SAPI_RETURN_IF_ERROR(AddFile(lib)); + } + + return ::sapi::OkStatus(); +} + +::sapi::Status Mounts::AddTmpfs(absl::string_view inside, size_t sz) { + MountTree::Node node; + auto tmpfs_node = node.mutable_tmpfs_node(); + tmpfs_node->set_tmpfs_options(absl::StrCat("size=", sz)); + return Insert(inside, node); +} + +namespace { + +uint64_t GetMountFlagsFor(const std::string& path) { + struct statvfs vfs; + if (TEMP_FAILURE_RETRY(statvfs(path.c_str(), &vfs)) == -1) { + SAPI_RAW_PLOG(ERROR, "statvfs"); + return 0; + } + + static constexpr struct { + const uint64_t mount_flag; + const uint64_t vfs_flag; + } mount_pairs[] = { + {MS_NOSUID, ST_NOSUID}, {MS_NODEV, ST_NODEV}, + {MS_NOEXEC, ST_NOEXEC}, {MS_SYNCHRONOUS, ST_SYNCHRONOUS}, + {MS_MANDLOCK, ST_MANDLOCK}, {MS_NOATIME, ST_NOATIME}, + {MS_NODIRATIME, ST_NODIRATIME}, {MS_RELATIME, ST_RELATIME}, + }; + + uint64_t flags = 0; + for (const auto& i : mount_pairs) { + if (vfs.f_flag & i.vfs_flag) { + flags |= i.mount_flag; + } + } + + return flags; +} + +void MountWithDefaults(const std::string& source, const std::string& target, + const char* fs_type, uint64_t extra_flags, + const char* option_str, bool is_ro) { + uint64_t flags = MS_REC | MS_NODEV | MS_NOSUID | extra_flags; + SAPI_RAW_VLOG(1, R"(mount("%s", "%s", "%s", %d, "%s"))", source, target, + fs_type, flags, option_str); + + int res = mount(source.c_str(), target.c_str(), fs_type, flags, option_str); + if (res == -1) { + if (errno == ENOENT) { + // File does not exist (anymore). This is e.g. the case when we're trying + // to gather stack-traces on SAPI crashes. The sandboxee application is a + // memfd file that is not existing anymore. + SAPI_RAW_LOG(WARNING, "Could not mount %s: file does not exist", source); + return; + } + SAPI_RAW_PLOG(FATAL, "mounting %s to %s failed", source, target); + } + + // Bind mounting as read-only doesn't work, we have to remount it. + if (is_ro) { + // Get actual mount flags. + flags |= GetMountFlagsFor(target); + res = mount("", target.c_str(), "", flags | MS_REMOUNT | MS_RDONLY, + option_str); + SAPI_RAW_PCHECK(res != -1, "remounting %s read-only failed", target); + } +} + +// Traverses the MountTree to create all required files and perform the mounts. +void CreateMounts(const MountTree& tree, const std::string& path, + bool create_backing_files) { + // First, create the backing files if needed. + if (create_backing_files) { + switch (tree.node().node_case()) { + case MountTree::Node::kFileNode: { + SAPI_RAW_VLOG(2, "Creating backing file at %s", path); + int fd = open(path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600); + SAPI_RAW_PCHECK(fd != -1, ""); + SAPI_RAW_PCHECK(close(fd) == 0, ""); + break; + } + case MountTree::Node::kDirNode: + case MountTree::Node::kTmpfsNode: + case MountTree::Node::NODE_NOT_SET: + SAPI_RAW_VLOG(2, "Creating directory at %s", path); + SAPI_RAW_PCHECK(mkdir(path.c_str(), 0700) == 0 || errno == EEXIST, ""); + break; + // Intentionally no default to make sure we handle all the cases. + } + } + + // Perform the actual mounts based on the node type. + switch (tree.node().node_case()) { + case MountTree::Node::kDirNode: { + // Since this directory is bind mounted, it's the users + // responsibility to make sure that all backing files are in place. + create_backing_files = false; + + auto node = tree.node().dir_node(); + MountWithDefaults(node.outside(), path, "", MS_BIND, nullptr, + node.is_ro()); + break; + } + case MountTree::Node::kTmpfsNode: { + // We can always create backing files under a tmpfs. + create_backing_files = true; + + auto node = tree.node().tmpfs_node(); + MountWithDefaults("", path, "tmpfs", 0, node.tmpfs_options().c_str(), + /* is_ro */ false); + break; + } + case MountTree::Node::kFileNode: { + auto node = tree.node().file_node(); + MountWithDefaults(node.outside(), path, "", MS_BIND, nullptr, + node.is_ro()); + + // A file node has to be a leaf so we can skip traversing here. + return; + } + case MountTree::Node::NODE_NOT_SET: + // Nothing to do, we already created the directory above. + break; + // Intentionally no default to make sure we handle all the cases. + } + + // Traverse the subtrees. + for (const auto& kv : tree.entries()) { + std::string new_path = file::JoinPath(path, kv.first); + CreateMounts(kv.second, new_path, create_backing_files); + } +} + +} // namespace + +void Mounts::CreateMounts(const std::string& root_path) const { + sandbox2::CreateMounts(mount_tree_, root_path, true); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/mounts.h b/sandboxed_api/sandbox2/mounts.h new file mode 100644 index 0000000..8ba346a --- /dev/null +++ b/sandboxed_api/sandbox2/mounts.h @@ -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_SANDBOX2_MOUNTTREE_H_ +#define SANDBOXED_API_SANDBOX2_MOUNTTREE_H_ + +#include +#include + +#include "absl/base/attributes.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/mounttree.pb.h" +#include "sandboxed_api/util/status.h" + +namespace sandbox2 { + +class Mounts { + public: + Mounts() = default; + explicit Mounts(MountTree mount_tree) : mount_tree_(std::move(mount_tree)) {} + + ::sapi::Status AddFile(absl::string_view path, bool is_ro = true); + + ::sapi::Status AddFileAt(absl::string_view outside, absl::string_view inside, + bool is_ro = true); + + ::sapi::Status AddDirectoryAt(absl::string_view outside, + absl::string_view inside, bool is_ro = true); + + ::sapi::Status AddMappingsForBinary(const std::string& path, + absl::string_view ld_library_path = {}); + + ::sapi::Status AddTmpfs(absl::string_view inside, size_t sz); + + void CreateMounts(const std::string& root_path) const; + + MountTree GetMountTree() const { return mount_tree_; } + + private: + friend class MountTreeTest; + ::sapi::Status Insert(absl::string_view path, const MountTree::Node& node); + MountTree mount_tree_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_MOUNTTREE_H_ diff --git a/sandboxed_api/sandbox2/mounts_test.cc b/sandboxed_api/sandbox2/mounts_test.cc new file mode 100644 index 0000000..f928c95 --- /dev/null +++ b/sandboxed_api/sandbox2/mounts_test.cc @@ -0,0 +1,171 @@ +// 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/mounts.h" + +#include + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/path.h" +#include "sandboxed_api/sandbox2/util/temp_file.h" +#include "sandboxed_api/util/status_matchers.h" + +using sapi::IsOk; +using sapi::StatusIs; +using ::testing::Eq; +using ::testing::StrEq; + +namespace sandbox2 { +namespace { + +constexpr size_t kTmpfsSize = 1024; + +TEST(MountTreeTest, TestInvalidFilenames) { + Mounts mounts; + + EXPECT_THAT(mounts.AddFile(""), StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddFile("a"), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddFileAt("/a", ""), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddFileAt("", "/a"), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddFileAt("/a", "a"), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddFile("/"), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddFileAt("/a", "/"), + StatusIs(sapi::StatusCode::kInvalidArgument)); +} + +TEST(MountTreeTest, TestAddFile) { + Mounts mounts; + + EXPECT_THAT(mounts.AddFile("/a"), IsOk()); + EXPECT_THAT(mounts.AddFile("/b"), IsOk()); + EXPECT_THAT(mounts.AddFile("/c/d"), IsOk()); + EXPECT_THAT(mounts.AddFile("/c/e"), IsOk()); + EXPECT_THAT(mounts.AddFile("/c/dd/e"), IsOk()); + + EXPECT_THAT(mounts.AddFileAt("/a", "/f"), IsOk()); +} + +TEST(MountTreeTest, TestAddDir) { + Mounts mounts; + + EXPECT_THAT(mounts.AddDirectoryAt("/a", "/a"), IsOk()); + EXPECT_THAT(mounts.AddDirectoryAt("/c/d", "/c/d"), IsOk()); + EXPECT_THAT(mounts.AddDirectoryAt("/c/d/e", "/c/d/e"), IsOk()); +} + +TEST(MountTreeTest, TestAddTmpFs) { + Mounts mounts; + + EXPECT_THAT(mounts.AddTmpfs("/a", kTmpfsSize), IsOk()); + EXPECT_THAT(mounts.AddTmpfs("/a/b", kTmpfsSize), IsOk()); + EXPECT_THAT(mounts.AddFile("/a/b/c"), IsOk()); + EXPECT_THAT(mounts.AddDirectoryAt("/a/b/d", "/a/b/d"), IsOk()); +} + +TEST(MountTreeTest, TestMultipleInsertionFileSymlink) { + Mounts mounts; + + auto result_or = CreateNamedTempFileAndClose( + file::JoinPath(GetTestTempPath(), "testdir_")); + ASSERT_THAT(result_or.status(), IsOk()); + std::string path = result_or.ValueOrDie(); + result_or = CreateNamedTempFileAndClose( + file::JoinPath(GetTestTempPath(), "testdir_")); + ASSERT_THAT(result_or.status(), IsOk()); + std::string symlink_path = result_or.ValueOrDie(); + ASSERT_THAT(unlink(symlink_path.c_str()), Eq(0)); + ASSERT_THAT(symlink(path.c_str(), symlink_path.c_str()), Eq(0)); + EXPECT_THAT(mounts.AddFileAt(path, "/a"), IsOk()); + EXPECT_THAT(mounts.AddFileAt(path, "/a"), IsOk()); + EXPECT_THAT(mounts.AddFileAt(symlink_path, "/a"), IsOk()); +} + +TEST(MountTreeTest, TestMultipleInsertionDirSymlink) { + Mounts mounts; + + auto result_or = CreateTempDir(file::JoinPath(GetTestTempPath(), "testdir_")); + ASSERT_THAT(result_or.status(), IsOk()); + std::string path = result_or.ValueOrDie(); + result_or = CreateNamedTempFileAndClose( + file::JoinPath(GetTestTempPath(), "testdir_")); + ASSERT_THAT(result_or.status(), IsOk()); + std::string symlink_path = result_or.ValueOrDie(); + ASSERT_THAT(unlink(symlink_path.c_str()), Eq(0)); + ASSERT_THAT(symlink(path.c_str(), symlink_path.c_str()), Eq(0)); + EXPECT_THAT(mounts.AddDirectoryAt(path, "/a"), IsOk()); + EXPECT_THAT(mounts.AddDirectoryAt(path, "/a"), IsOk()); + EXPECT_THAT(mounts.AddDirectoryAt(symlink_path, "/a"), IsOk()); + EXPECT_THAT(mounts.AddDirectoryAt(symlink_path, "/a"), IsOk()); +} + +TEST(MountTreeTest, TestMultipleInsertion) { + Mounts mounts; + + EXPECT_THAT(mounts.AddFile("/c/d"), IsOk()); + + EXPECT_THAT(mounts.AddFile("/c"), + StatusIs(sapi::StatusCode::kFailedPrecondition)); + EXPECT_THAT(mounts.AddFileAt("/f", "/c"), + StatusIs(sapi::StatusCode::kFailedPrecondition)); + EXPECT_THAT(mounts.AddDirectoryAt("/f", "/c"), IsOk()); + + EXPECT_THAT(mounts.AddFile("/c/d/e"), + StatusIs(sapi::StatusCode::kFailedPrecondition)); + EXPECT_THAT(mounts.AddFileAt("/f", "/c/d/e"), + StatusIs(sapi::StatusCode::kFailedPrecondition)); + EXPECT_THAT(mounts.AddDirectoryAt("/f", "/c/d/e"), + StatusIs(sapi::StatusCode::kFailedPrecondition)); +} + +TEST(MountTreeTest, TestEvilNullByte) { + Mounts mounts; + // create the filename with a null byte this way as g4 fix forces newlines + // otherwise. + std::string filename{"/a/b"}; + filename[2] = '\0'; + + EXPECT_THAT(mounts.AddFile(filename), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddFileAt(filename, "/a"), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddFileAt("/a", filename), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddDirectoryAt(filename, "/a"), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddDirectoryAt("/a", filename), + StatusIs(sapi::StatusCode::kInvalidArgument)); + EXPECT_THAT(mounts.AddTmpfs(filename, kTmpfsSize), + StatusIs(sapi::StatusCode::kInvalidArgument)); +} + +TEST(MountTreeTest, TestMinimalDynamicBinary) { + Mounts mounts; + EXPECT_THAT(mounts.AddMappingsForBinary( + GetTestSourcePath("sandbox2/testcases/minimal_dynamic")), + IsOk()); + EXPECT_THAT(mounts.AddFile("/lib/x86_64-linux-gnu/libc.so.6"), IsOk()); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/mounttree.proto b/sandboxed_api/sandbox2/mounttree.proto new file mode 100644 index 0000000..53568c3 --- /dev/null +++ b/sandboxed_api/sandbox2/mounttree.proto @@ -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. + +// A proto for serializing the sandbox2::MountTree class + +syntax = "proto2"; +package sandbox2; + +// The MountTree maps path components to mount operations (bind/tmpfs). The path +// is encoded in the key of the entries map, with the root node representing /. +// To get the full path of a node, you will need to assemble the keys starting +// at the root node. +message MountTree { + // FileNode represents a bind mount for a regular file using "outside" as the + // source. + message FileNode { + required string outside = 2; + required bool is_ro = 3; + } + + // DirNode is like FileNode but for directories. + message DirNode { + required string outside = 2; + required bool is_ro = 3; + } + + // TmpfsNode mounts a tmpfs with given options. + message TmpfsNode { + required string tmpfs_options = 1; + } + + message Node { + oneof node { + FileNode file_node = 1; + DirNode dir_node = 2; + TmpfsNode tmpfs_node = 3; + } + } + + // The entries are mappings from the next path component to the subtree. + map entries = 1; + + // The node of the current path. If not set, we'll just create a directory at + // this position. + optional Node node = 2; +} diff --git a/sandboxed_api/sandbox2/namespace.cc b/sandboxed_api/sandbox2/namespace.cc new file mode 100644 index 0000000..d36d740 --- /dev/null +++ b/sandboxed_api/sandbox2/namespace.cc @@ -0,0 +1,265 @@ +// 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::Namespace class. + +#include "sandboxed_api/sandbox2/namespace.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.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/path.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/raw_logging.h" + +namespace sandbox2 { + +static constexpr char kSandbox2ChrootPath[] = "/tmp/.sandbox2chroot"; + +namespace { +int MountFallbackToReadOnly(const char* source, const char* target, + const char* filesystem, uintptr_t flags, + const void* data) { + int rv = mount(source, target, filesystem, flags, data); + if (rv != 0 && (flags & MS_RDONLY) == 0) { + SAPI_RAW_LOG(WARNING, + "Mounting %s on %s (fs type %s) read-write failed: %s", source, + target, filesystem, StrError(errno)); + rv = mount(source, target, filesystem, flags | MS_RDONLY, data); + if (rv == 0) { + SAPI_RAW_LOG(INFO, "Mounted %s on %s (fs type %s) as read-only", source, + target, filesystem); + } + } + return rv; +} +} // namespace + +void PrepareChroot(const Mounts& mounts) { + // Create a tmpfs mount for the new rootfs. + SAPI_RAW_CHECK(util::CreateDirRecursive(kSandbox2ChrootPath, 0700), + "could not create directory for rootfs"); + SAPI_RAW_PCHECK(mount("none", kSandbox2ChrootPath, "tmpfs", 0, nullptr) == 0, + "mounting rootfs failed"); + + // Walk the tree and perform all the mount operations. + mounts.CreateMounts(kSandbox2ChrootPath); + + SAPI_RAW_PCHECK( + mount("/", "/", "", MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr) != -1, + "Remounting / RO failed"); +} + +void TryDenySetgroups() { + int fd = open("/proc/self/setgroups", O_WRONLY); + // We ignore errors since they are most likely due to an old kernel. + if (fd == -1) { + return; + } + + file_util::fileops::FDCloser fd_closer{fd}; + + dprintf(fd, "deny"); +} + +void WriteIDMap(const char* map_path, int32_t uid) { + int fd = open(map_path, O_WRONLY); + SAPI_RAW_PCHECK(fd != -1, "Couldn't open %s", map_path); + + file_util::fileops::FDCloser fd_closer{fd}; + + SAPI_RAW_PCHECK(dprintf(fd, "1000 %d 1", uid) >= 0, + "Could not write %d to %s", uid, map_path); +} + +void ActivateLoopbackInterface() { + ifreq ifreq; + + ifreq.ifr_flags = 0; + strncpy(ifreq.ifr_name, "lo", IFNAMSIZ); + + // Create an AF_INET6 socket to perform the IF FLAGS ioctls on. + int fd = socket(AF_INET6, SOCK_DGRAM, 0); + SAPI_RAW_PCHECK(fd != -1, "creating socket for activating loopback failed"); + + file_util::fileops::FDCloser fd_closer{fd}; + + // First get the existing flags. + SAPI_RAW_PCHECK(ioctl(fd, SIOCGIFFLAGS, &ifreq) != -1, + "Getting existing flags"); + + // From 812 kernels, we don't have CAP_NET_ADMIN anymore. But the interface is + // already up, so we can skip the next ioctl. + if (ifreq.ifr_flags & IFF_UP) { + return; + } + + // Set the UP flag and write the flags back. + ifreq.ifr_flags |= IFF_UP; + SAPI_RAW_PCHECK(ioctl(fd, SIOCSIFFLAGS, &ifreq) != -1, "Setting IFF_UP flag"); +} + +// Logs the filesystem contents if verbose logging is enabled. +void LogFilesystem(const std::string& dir) { + std::vector entries; + std::string error; + if (!file_util::fileops::ListDirectoryEntries(dir, &entries, &error)) { + SAPI_RAW_PLOG(ERROR, "could not list directory entries for %s", dir); + return; + } + + for (const auto& entry : entries) { + struct stat64 st; + std::string full_path = file::JoinPath(dir, entry); + if (lstat64(full_path.c_str(), &st) != 0) { + SAPI_RAW_PLOG(ERROR, "could not stat %s", full_path); + continue; + } + + char ftype; + switch (st.st_mode & S_IFMT) { + case S_IFREG: + ftype = '-'; + break; + case S_IFDIR: + ftype = 'd'; + break; + case S_IFLNK: + ftype = 'l'; + break; + default: + ftype = '?'; + break; + } + + std::string type_and_mode; + type_and_mode += ftype; + type_and_mode += st.st_mode & S_IRUSR ? 'r' : '-'; + type_and_mode += st.st_mode & S_IWUSR ? 'w' : '-'; + type_and_mode += st.st_mode & S_IXUSR ? 'x' : '-'; + type_and_mode += st.st_mode & S_IRGRP ? 'r' : '-'; + type_and_mode += st.st_mode & S_IWGRP ? 'w' : '-'; + type_and_mode += st.st_mode & S_IXGRP ? 'x' : '-'; + type_and_mode += st.st_mode & S_IROTH ? 'r' : '-'; + type_and_mode += st.st_mode & S_IWOTH ? 'w' : '-'; + type_and_mode += st.st_mode & S_IXOTH ? 'x' : '-'; + + std::string link; + if (S_ISLNK(st.st_mode)) { + link = absl::StrCat(" -> ", file_util::fileops::ReadLink(full_path)); + } + SAPI_RAW_VLOG(2, "%s %s%s", type_and_mode, full_path, link); + + if (S_ISDIR(st.st_mode)) { + LogFilesystem(full_path); + } + } +} + +Namespace::Namespace(bool allow_unrestricted_networking, Mounts mounts, + std::string hostname) + : clone_flags_(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWPID | + CLONE_NEWIPC), + mounts_(std::move(mounts)), + hostname_(std::move(hostname)) { + if (!allow_unrestricted_networking) { + clone_flags_ |= CLONE_NEWNET; + } +} + +void Namespace::DisableUserNamespace() { clone_flags_ &= ~CLONE_NEWUSER; } + +int32_t Namespace::GetCloneFlags() const { return clone_flags_; } + +void Namespace::InitializeNamespaces(uid_t uid, gid_t gid, int32_t clone_flags, + const Mounts& mounts, bool mount_proc, + const std::string& hostname) { + if (clone_flags & CLONE_NEWUSER) { + // Set up the uid and gid map. + TryDenySetgroups(); + WriteIDMap("/proc/self/uid_map", uid); + WriteIDMap("/proc/self/gid_map", gid); + } + + if (!(clone_flags & CLONE_NEWNS)) { + // CLONE_NEWNS is always set if we're running in namespaces. + return; + } + + SAPI_RAW_PCHECK( + !mount_proc || mount("", "/proc", "proc", + MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr) != -1, + "Could not mount a new /proc" + ); + + if (clone_flags & CLONE_NEWNET) { + // Some things can only be done if inside a new network namespace, like + // mounting /sys, setting a hostname or bringing up lo if necessary. + + SAPI_RAW_PCHECK( + MountFallbackToReadOnly("", "/sys", "sysfs", + MS_NODEV | MS_NOEXEC | MS_NOSUID, + nullptr) != -1, + "Could not mount a new /sys" + ); + + SAPI_RAW_PCHECK(sethostname(hostname.c_str(), hostname.size()) != -1, + "Could not set network namespace hostname '%s'", hostname); + ActivateLoopbackInterface(); + } + + PrepareChroot(mounts); + + // This requires some explanation: It's actually possible to pivot_root('/', + // '/'). After this operation has been completed, the old root is mounted over + // the new root, and it's OK to simply umount('/') now, and to have new_root + // as '/'. This allows us not care about providing any special directory for + // old_root, which is sometimes not easy, given that e.g. /tmp might not + // always be present inside new_root. + SAPI_RAW_PCHECK( + syscall(__NR_pivot_root, kSandbox2ChrootPath, kSandbox2ChrootPath) != -1, + "pivot root"); + SAPI_RAW_PCHECK(umount2("/", MNT_DETACH) != -1, "detaching old root"); + + if (SAPI_VLOG_IS_ON(2)) { + SAPI_RAW_VLOG(2, "Dumping the sandboxee's filesystem:"); + LogFilesystem("/"); + } +} + +void Namespace::GetNamespaceDescription(NamespaceDescription* pb_description) { + pb_description->set_clone_flags(clone_flags_); + *pb_description->mutable_mount_tree_mounts() = mounts_.GetMountTree(); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/namespace.h b/sandboxed_api/sandbox2/namespace.h new file mode 100644 index 0000000..2848f2a --- /dev/null +++ b/sandboxed_api/sandbox2/namespace.h @@ -0,0 +1,69 @@ +// 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::Namespace class defines ways of inserting the sandboxed process +// into Linux namespaces. + +#ifndef SANDBOXED_API_SANDBOX2_NAMESPACE_H_ +#define SANDBOXED_API_SANDBOX2_NAMESPACE_H_ + +#include + +#include +#include + +#include "absl/base/macros.h" +#include "sandboxed_api/sandbox2/deathrattle_fatalmsg.pb.h" +#include "sandboxed_api/sandbox2/mounts.h" + +namespace sandbox2 { + +class Namespace final { + public: + // Performs the namespace setup (mounts, write the uid_map, etc.). + static void InitializeNamespaces(uid_t uid, gid_t gid, int32_t clone_flags, + const Mounts& mounts, bool mount_proc, + const std::string& hostname); + + Namespace() = delete; + Namespace(const Namespace&) = delete; + Namespace& operator=(const Namespace&) = delete; + + Namespace(bool allow_unrestricted_networking, Mounts mounts, std::string hostname); + + void DisableUserNamespace(); + + // Returns all needed CLONE_NEW* flags. + int32_t GetCloneFlags() const; + + // Stores information about this namespace in the protobuf structure. + void GetNamespaceDescription(NamespaceDescription* pb_description); + + Mounts& mounts() { return mounts_; } + const Mounts& mounts() const { return mounts_; } + + const std::string& hostname() const { return hostname_; } + const std::string& GetHostname() const { return hostname_; } + + private: + friend class StackTracePeer; + + int32_t clone_flags_; + Mounts mounts_; + std::string hostname_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_NAMESPACE_H_ diff --git a/sandboxed_api/sandbox2/namespace_test.cc b/sandboxed_api/sandbox2/namespace_test.cc new file mode 100644 index 0000000..702570a --- /dev/null +++ b/sandboxed_api/sandbox2/namespace_test.cc @@ -0,0 +1,182 @@ +// 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/namespace.h" + +#include +#include +#include +#include + +#include + +#include +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/executor.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/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/util/status_matchers.h" + +namespace sandbox2 { +namespace { + +TEST(NamespaceTest, FileNamespaceWorks) { + // Mount /binary_path RO and check that it actually is RO. + // /etc/passwd should not exist. + const std::string path = GetTestSourcePath("sandbox2/testcases/namespace"); + std::vector args = {path, "0", "/binary_path", "/etc/passwd"}; + auto executor = absl::make_unique(path, args); + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all + .DangerDefaultAllowAll() + .EnableNamespaces() + .AddFileAt(path, "/binary_path") + .TryBuild()); + + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); + EXPECT_EQ(result.reason_code(), 2); +} + +TEST(NamespaceTest, UserNamespaceWorks) { + // Check that getpid() returns 2 (which is the case inside pid NS). + const std::string path = GetTestSourcePath("sandbox2/testcases/namespace"); + std::vector args = {path, "2"}; + { + auto executor = absl::make_unique(path, args); + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all + .DangerDefaultAllowAll() + .EnableNamespaces() + .TryBuild()); + + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); + EXPECT_EQ(result.reason_code(), 0); + } + + // Validate that getpid() does not return 2 when outside of an pid NS. + { + auto executor = absl::make_unique(path, args); + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all + .DangerDefaultAllowAll() + .TryBuild()); + + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); + EXPECT_NE(result.reason_code(), 0); + } +} + +TEST(NamespaceTest, UserNamespaceIDMapWritten) { + // Check that the idmap is initialized before the sandbox application is + // started. + const std::string path = GetTestSourcePath("sandbox2/testcases/namespace"); + { + std::vector args = {path, "3", "1000", "1000"}; + auto executor = absl::make_unique(path, args); + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all + .DangerDefaultAllowAll() + .EnableNamespaces() + .TryBuild()); + + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); + EXPECT_EQ(result.reason_code(), 0); + } + + // Check that the uid/gid is the same when not using namespaces. + { + const std::string uid = absl::StrCat(getuid()); + const std::string gid = absl::StrCat(getgid()); + std::vector args = {path, "3", uid, gid}; + auto executor = absl::make_unique(path, args); + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all + .DangerDefaultAllowAll() + .TryBuild()); + + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); + EXPECT_EQ(result.reason_code(), 0); + } +} + +class HostnameTest : public testing::Test { + protected: + void Try(std::string arg, std::unique_ptr policy) { + const std::string path = GetTestSourcePath("sandbox2/testcases/hostname"); + std::vector args = {path, std::move(arg)}; + auto executor = absl::make_unique(path, args); + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + ASSERT_EQ(result.final_status(), Result::OK); + code_ = result.reason_code(); + } + + int code_; +}; + +TEST_F(HostnameTest, None) { + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all + .DangerDefaultAllowAll() + .TryBuild()); + Try("sandbox2", std::move(policy)); + EXPECT_EQ(code_, 1); +} + +TEST_F(HostnameTest, Default) { + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all + .DangerDefaultAllowAll() + .EnableNamespaces() + .TryBuild()); + Try("sandbox2", std::move(policy)); + EXPECT_EQ(code_, 0); +} + +TEST_F(HostnameTest, Configured) { + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all + .DangerDefaultAllowAll() + .SetHostname("configured") + .TryBuild()); + Try("configured", std::move(policy)); + EXPECT_EQ(code_, 0); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy_client.cc b/sandboxed_api/sandbox2/network_proxy_client.cc new file mode 100644 index 0000000..be7dc45 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy_client.cc @@ -0,0 +1,94 @@ +// 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/network_proxy_client.h" + +#include +#include + +#include +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/canonical_errors.h" +#include "sandboxed_api/util/status.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sandbox2 { + +constexpr char NetworkProxyClient::kFDName[]; + +int NetworkProxyClient::ConnectHandler(int sockfd, const struct sockaddr* addr, + socklen_t addrlen) { + sapi::Status status = Connect(sockfd, addr, addrlen); + if (status.ok()) { + return 0; + } + PLOG(ERROR) << "ConnectHandler() failed: " << status.message(); + return -1; +} + +sapi::Status NetworkProxyClient::Connect(int sockfd, + const struct sockaddr* addr, + socklen_t addrlen) { + absl::MutexLock lock(&mutex_); + + // Check if socket is SOCK_STREAM + int type; + socklen_t type_size = sizeof(int); + int result = getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &type, &type_size); + if (result == -1) { + return sapi::FailedPreconditionError("Invalid socket FD"); + } + if (type_size != sizeof(int) || type != SOCK_STREAM) { + errno = EINVAL; + return sapi::InvalidArgumentError( + "Invalid socket, only SOCK_STREAM is allowed"); + } + + // Send sockaddr struct + if (!comms_.SendBytes(reinterpret_cast(addr), addrlen)) { + errno = EIO; + return sapi::InternalError("Sending data to network proxy failed"); + } + + SAPI_RETURN_IF_ERROR(ReceiveRemoteResult()); + + // Receive new socket + int s; + if (!comms_.RecvFD(&s)) { + errno = EIO; + return sapi::InternalError("Receiving data from network proxy failed"); + } + if (dup2(s, sockfd) == -1) { + close(s); + return sapi::InternalError("Processing data from network proxy failed"); + } + return sapi::OkStatus(); +} + +sapi::Status NetworkProxyClient::ReceiveRemoteResult() { + int result; + if (!comms_.RecvInt32(&result)) { + errno = EIO; + return sapi::InternalError("Receiving data from the network proxy failed"); + } + if (result != 0) { + errno = result; + return sapi::InternalError( + absl::StrCat("Error in network proxy: ", StrError(errno))); + } + return sapi::OkStatus(); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy_client.h b/sandboxed_api/sandbox2/network_proxy_client.h new file mode 100644 index 0000000..a013c53 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy_client.h @@ -0,0 +1,55 @@ +// 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_NETWORK_PROXY_CLIENT_H_ +#define SANDBOXED_API_SANDBOX2_NETWORK_PROXY_CLIENT_H_ + +#include + +#include "absl/synchronization/mutex.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/util/status.h" + +namespace sandbox2 { + +class NetworkProxyClient { + public: + static constexpr char kFDName[] = "sb2_networkproxy"; + + explicit NetworkProxyClient(int fd) : comms_(fd) {} + + NetworkProxyClient(const NetworkProxyClient&) = delete; + NetworkProxyClient& operator=(const NetworkProxyClient&) = delete; + + // Establishes a new network connection. + // Semantic is similar to a regular connect() call. + // Arguments are sent to network proxy server, which sends back a connected + // socket. + sapi::Status Connect(int sockfd, const struct sockaddr* addr, + socklen_t addrlen); + // Same as Connect, but with same API as regular connect() call. + int ConnectHandler(int sockfd, const struct sockaddr* addr, + socklen_t addrlen); + + private: + Comms comms_; + sapi::Status ReceiveRemoteResult(); + + // Needed to make the Proxy thread safe. + absl::Mutex mutex_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_NETWORK_PROXY_CLIENT_H_ diff --git a/sandboxed_api/sandbox2/network_proxy_server.cc b/sandboxed_api/sandbox2/network_proxy_server.cc new file mode 100644 index 0000000..8394971 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy_server.cc @@ -0,0 +1,86 @@ +// 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/network_proxy_server.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "absl/memory/memory.h" +#include "sandboxed_api/sandbox2/util/fileops.h" + +namespace sandbox2 { + +NetworkProxyServer::NetworkProxyServer(int fd) + : comms_{absl::make_unique(fd)}, fatal_error_{false} {} + +void NetworkProxyServer::ProcessConnectRequest() { + std::vector addr; + if (!comms_->RecvBytes(&addr)) { + fatal_error_ = true; + return; + } + + // Only sockaddr_in is supported. + if (addr.size() != sizeof(sockaddr_in)) { + SendResult(-1, EINVAL); + return; + } + const struct sockaddr_in* saddr = + reinterpret_cast(addr.data()); + + // Only IPv4 TCP and IPv6 TCP are supported. + if (saddr->sin_family != AF_INET && saddr->sin_family != AF_INET6) { + SendResult(-1, EINVAL); + return; + } + + int new_socket = socket(saddr->sin_family, SOCK_STREAM, 0); + file_util::fileops::FDCloser new_socket_closer(new_socket); + + int result = connect( + new_socket, reinterpret_cast(addr.data()), addr.size()); + + SendResult(result, errno); + + if (result == 0 && !fatal_error_) { + if (!comms_->SendFD(new_socket)) { + fatal_error_ = true; + return; + } + } +} + +void NetworkProxyServer::Run() { + while (!fatal_error_) { + ProcessConnectRequest(); + } + LOG(INFO) + << "Clean shutdown or error occurred, shutting down NetworkProxyServer"; +} + +void NetworkProxyServer::SendResult(int result, int saved_errno) { + if (!comms_->SendInt32(result == 0 ? result : saved_errno)) { + fatal_error_ = true; + } +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy_server.h b/sandboxed_api/sandbox2/network_proxy_server.h new file mode 100644 index 0000000..91c6b24 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy_server.h @@ -0,0 +1,51 @@ +// 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_NETWORK_PROXY_SERVER_H_ +#define SANDBOXED_API_SANDBOX2_NETWORK_PROXY_SERVER_H_ + +#include + +#include "sandboxed_api/sandbox2/comms.h" + +namespace sandbox2 { + +// This is a proxy server that spawns connected sockets on requests. +// Then it sends the file descriptor to the requestor. It is used to get around +// limitations created by network namespaces. +class NetworkProxyServer { + public: + explicit NetworkProxyServer(int fd); + + NetworkProxyServer(const NetworkProxyServer&) = delete; + NetworkProxyServer& operator=(const NetworkProxyServer&) = delete; + + // Starts handling incoming connection requests. + void Run(); + + private: + // Sends the result of internal functions to the sandboxee. It sends errno in + // case of error and 0 if no error occurred. On error, it sets fatal_error_ to + // true, which terminates the processing loop in ProcessConnectRequest(). + void SendResult(int result, int saved_errno); + + void ProcessConnectRequest(); + + std::unique_ptr comms_; + bool fatal_error_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_NETWORK_PROXY_SERVER_H_ diff --git a/sandboxed_api/sandbox2/notify.h b/sandboxed_api/sandbox2/notify.h new file mode 100644 index 0000000..f773e6e --- /dev/null +++ b/sandboxed_api/sandbox2/notify.h @@ -0,0 +1,64 @@ +// 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::Notify class handless exceptional situations in the sandbox + +#ifndef SANDBOXED_API_SANDBOX2_NOTIFY_H_ +#define SANDBOXED_API_SANDBOX2_NOTIFY_H_ + +#include + +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/syscall.h" + +namespace sandbox2 { + +enum ViolationType { + // A syscall disallowed by the policy was invoked. + kSyscallViolation, + // A syscall with cpu architecture not covered by the policy was invoked. + kArchitectureSwitchViolation, +}; + +class Notify { + public: + virtual ~Notify() = default; + + // Called when a process has been created and executed, but not yet sandboxed. + // Using comms only makes sense if the client is sandboxed in the + // Executor::set_enable_sandbox_before_exec(false) mode. + // Returns a success indicator: false will cause the Sandbox Monitor to return + // sandbox2::Result::SETUP_ERROR for Run()/RunAsync(). + virtual bool EventStarted(pid_t pid, Comms* comms) { return true; } + + // Called when all sandboxed processes finished. + virtual void EventFinished(const Result& result) {} + + // Called when a process exited with a syscall violation. + virtual void EventSyscallViolation(const Syscall& syscall, + ViolationType type) {} + + // Called when a policy called TRACE. The syscall is allowed if this method + // returns true. + // This allows for implementing 'log, but allow' policies. + virtual bool EventSyscallTrap(const Syscall& syscall) { return false; } + + // Called when a process received a signal. + virtual void EventSignal(pid_t pid, int sig_no) {} +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_NOTIFY_H_ diff --git a/sandboxed_api/sandbox2/notify_test.cc b/sandboxed_api/sandbox2/notify_test.cc new file mode 100644 index 0000000..fb285fb --- /dev/null +++ b/sandboxed_api/sandbox2/notify_test.cc @@ -0,0 +1,146 @@ +// 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/notify.h" + +#include + +#include +#include +#include +#include + +#include +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_join.h" +#include "sandboxed_api/sandbox2/comms.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/syscall.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" + +namespace sandbox2 { +namespace { + +// Allow typical syscalls and call SECCOMP_RET_TRACE for personality syscall, +// chosen because unlikely to be called by a regular program. +std::unique_ptr NotifyTestcasePolicy() { + return PolicyBuilder() + .AllowStaticStartup() + .AllowExit() + .AllowRead() + .AllowWrite() + .AllowSyscall(__NR_close) + .AddPolicyOnSyscall(__NR_personality, {SANDBOX2_TRACE}) + .BlockSyscallWithErrno(__NR_open, ENOENT) + .BlockSyscallWithErrno(__NR_access, ENOENT) + .BlockSyscallWithErrno(__NR_openat, ENOENT) + .BlockSyscallWithErrno(__NR_prlimit64, EPERM) + .BuildOrDie(); +} + +// If syscall and its arguments don't match the expected ones, return the +// opposite of the requested values (allow/disallow) to indicate an error. +class PersonalityNotify : public Notify { + public: + explicit PersonalityNotify(bool allow) : allow_(allow) {} + + bool EventSyscallTrap(const Syscall& syscall) override { + if (syscall.nr() != __NR_personality) { + LOG(ERROR) << "kSyscall==" << syscall.nr(); + return (!allow_); + } + Syscall::Args expected_args = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6}; + if (syscall.args() != expected_args) { + LOG(ERROR) << "args=={" << absl::StrJoin(syscall.args(), ", ") << "}"; + return (!allow_); + } + return allow_; + } + + private: + // The intended return value from EventSyscallTrap in case all registers + // match. + bool allow_; +}; + +// Print the newly created PID, and exchange data over Comms before sandboxing. +class PidCommsNotify : public Notify { + public: + bool EventStarted(pid_t pid, Comms* comms) final { + LOG(INFO) << "The newly created PID: " << pid; + bool v; + return comms->RecvBool(&v); + } +}; + +// Test EventSyscallTrap on personality syscall and allow it. +TEST(NotifyTest, AllowPersonality) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/personality"); + std::vector args = {path}; + auto executor = absl::make_unique(path, args); + auto policy = NotifyTestcasePolicy(); + ASSERT_THAT(policy, testing::Not(testing::IsNull())); + auto notify = absl::make_unique(true); + + Sandbox2 s2(std::move(executor), std::move(policy), std::move(notify)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); + ASSERT_EQ(result.reason_code(), 22); +} + +// Test EventSyscallTrap on personality syscall and disallow it. +TEST(NotifyTest, DisallowPersonality) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/personality"); + std::vector args = {path}; + auto executor = absl::make_unique(path, args); + auto policy = NotifyTestcasePolicy(); + ASSERT_THAT(policy, testing::Not(testing::IsNull())); + auto notify = absl::make_unique(false); + + Sandbox2 s2(std::move(executor), std::move(policy), std::move(notify)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), Result::VIOLATION); + ASSERT_EQ(result.reason_code(), __NR_personality); +} + +// Test EventStarted by exchanging data after started but before sandboxed. +TEST(NotifyTest, PrintPidAndComms) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/pidcomms"); + std::vector args = {path}; + auto executor = absl::make_unique(path, args); + executor->set_enable_sandbox_before_exec(false); + auto policy = NotifyTestcasePolicy(); + ASSERT_THAT(policy, testing::Not(testing::IsNull())); + auto notify = absl::make_unique(); + + Sandbox2 s2(std::move(executor), std::move(policy), std::move(notify)); + auto result = s2.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); + ASSERT_EQ(result.reason_code(), 33); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/policy.cc b/sandboxed_api/sandbox2/policy.cc new file mode 100644 index 0000000..00b2152 --- /dev/null +++ b/sandboxed_api/sandbox2/policy.cc @@ -0,0 +1,180 @@ +// 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::Policy class. + +#include "sandboxed_api/sandbox2/policy.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "sandboxed_api/util/flag.h" +#include +#include "sandboxed_api/sandbox2/bpfdisassembler.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/regs.h" +#include "sandboxed_api/sandbox2/syscall.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" + +ABSL_FLAG(bool, sandbox2_danger_danger_permit_all, false, + "Allow all syscalls, useful for testing"); + +ABSL_FLAG(string, sandbox2_danger_danger_permit_all_and_log, "", + "Allow all syscalls and log them into specified file"); + +namespace sandbox2 { + +// The final policy is the concatenation of: +// 1. default policy (GetDefaultPolicy, private), +// 2. user policy (user_policy_, public), +// 3. default KILL action (avoid failing open if user policy did not do it). +std::vector Policy::GetPolicy() const { + if (absl::GetFlag(FLAGS_sandbox2_danger_danger_permit_all) || + !absl::GetFlag(FLAGS_sandbox2_danger_danger_permit_all_and_log).empty()) { + return GetTrackingPolicy(); + } + + // Now we can start building the policy. + // 1. Start with the default policy (e.g. syscall architecture checks). + auto policy = GetDefaultPolicy(); + VLOG(3) << "Default policy:\n" << bpf::Disasm(policy); + + // 2. Append user policy. + VLOG(3) << "User policy:\n" << bpf::Disasm(user_policy_); + // Add default syscall_nr loading in case the user forgets. + policy.push_back(LOAD_SYSCALL_NR); + policy.insert(policy.end(), user_policy_.begin(), user_policy_.end()); + + // 3. Finish with default KILL action. + policy.push_back(KILL); + + VLOG(2) << "Final policy:\n" << bpf::Disasm(policy); + return policy; +} + +// If you modify this function, you should also modify. +// Monitor::LogAccessViolation to keep them in sync. +// +// Produces a policy which returns SECCOMP_RET_TRACE instead of SECCOMP_RET_KILL +// for the __NR_execve syscall, so the tracer can make a decision to allow or +// disallow it depending on which occurrence of __NR_execve it was. +// LINT.IfChange +std::vector Policy::GetDefaultPolicy() const { + bpf_labels l = {0}; + + std::vector policy = { + // If compiled arch is different than the runtime one, inform the Monitor. + LOAD_ARCH, + JEQ32(Syscall::GetHostAuditArch(), JUMP(&l, past_arch_check_l)), + JEQ32(AUDIT_ARCH_X86_64, TRACE(Syscall::kX86_64)), + JEQ32(AUDIT_ARCH_I386, TRACE(Syscall::kX86_32)), + JEQ32(AUDIT_ARCH_PPC64LE, TRACE(Syscall::kPPC_64)), + TRACE(Syscall::kUnknown), + LABEL(&l, past_arch_check_l), + + // After the policy is uploaded, forkserver will execve the sandboxee. We + // need to allow this execve but not others. Since BPF does not have + // state, we need to inform the Monitor to decide, and for that we use a + // magic value in syscall args 5. Note that this value is not supposed to + // be secret, but just an optimization so that the monitor is not + // triggered on every call to execveat. + LOAD_SYSCALL_NR, + JNE32(__NR_execveat, JUMP(&l, past_execveat_l)), + ARG_32(4), + JNE32(AT_EMPTY_PATH, JUMP(&l, past_execveat_l)), + ARG_32(5), + JNE32(internal::kExecveMagic, JUMP(&l, past_execveat_l)), + SANDBOX2_TRACE, + LABEL(&l, past_execveat_l), + + // Forbid some syscalls because unsafe or too risky. + LOAD_SYSCALL_NR, + JEQ32(__NR_ptrace, DENY), + JEQ32(__NR_bpf, DENY), + + // Disallow clone with CLONE_UNTRACED flag. + JNE32(__NR_clone, JUMP(&l, past_clone_untraced_l)), + // Regardless of arch, we only care about the lower 32-bits of the flags. + ARG_32(0), + JA32(CLONE_UNTRACED, DENY), + LABEL(&l, past_clone_untraced_l), + }; + + if (bpf_resolve_jumps(&l, policy.data(), policy.size()) != 0) { + LOG(FATAL) << "Cannot resolve bpf jumps"; + } + + return policy; +} +// LINT.ThenChange(monitor.cc) + +std::vector Policy::GetTrackingPolicy() const { + return { + LOAD_ARCH, + JEQ32(AUDIT_ARCH_X86_64, TRACE(Syscall::kX86_64)), + JEQ32(AUDIT_ARCH_I386, TRACE(Syscall::kX86_32)), + JEQ32(AUDIT_ARCH_PPC64LE, TRACE(Syscall::kPPC_64)), + TRACE(Syscall::kUnknown), + }; +} + +bool Policy::SendPolicy(Comms* comms) const { + auto policy = GetPolicy(); + if (!comms->SendBytes( + reinterpret_cast(policy.data()), + static_cast(policy.size()) * sizeof(sock_filter))) { + LOG(ERROR) << "Couldn't send policy"; + return false; + } + + return true; +} + +void Policy::AllowUnsafeKeepCapabilities( + std::unique_ptr> caps) { + if (namespace_) { + namespace_->DisableUserNamespace(); + } + capabilities_ = std::move(caps); +} + +void Policy::GetPolicyDescription(PolicyDescription* policy) const { + policy->set_user_bpf_policy(user_policy_.data(), + user_policy_.size() * sizeof(sock_filter)); + if (policy_builder_description_) { + *policy->mutable_policy_builder_description() = + *policy_builder_description_; + } + + if (namespace_) { + namespace_->GetNamespaceDescription( + policy->mutable_namespace_description()); + } + + if (capabilities_) { + for (const auto& cap : *capabilities_) { + policy->add_capabilities(cap); + } + } +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/policy.h b/sandboxed_api/sandbox2/policy.h new file mode 100644 index 0000000..931ef03 --- /dev/null +++ b/sandboxed_api/sandbox2/policy.h @@ -0,0 +1,115 @@ +// 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::Policy class provides methods for manipulating seccomp-bpf +// syscall policies. + +#ifndef SANDBOXED_API_SANDBOX2_POLICY_H_ +#define SANDBOXED_API_SANDBOX2_POLICY_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "absl/base/macros.h" +#include "absl/types/optional.h" +#include "sandboxed_api/sandbox2/deathrattle_fatalmsg.pb.h" +#include "sandboxed_api/sandbox2/namespace.h" +#include "sandboxed_api/sandbox2/syscall.h" + +#define SANDBOX2_TRACE TRACE(::sandbox2::Syscall::GetHostArch()) + +namespace sandbox2 { + +namespace internal { +// Magic values of registers when executing sys_execveat, so we can recognize +// the pre-sandboxing state and notify the Monitor +constexpr uintptr_t kExecveMagic = 0x921c2c34; +} // namespace internal + +class Comms; + +class Policy final { + public: + + // Skips creation of a user namespace and keep capabilities in the global + // namespace. This only makes sense in some rare cases where the sandbox is + // started as root, please talk to sandbox-team@ before using this function. + void AllowUnsafeKeepCapabilities( + std::unique_ptr> caps); + + // Stores information about the policy (and the policy builder if existing) + // in the protobuf structure. + void GetPolicyDescription(PolicyDescription* policy) const; + + private: + // Private constructor only called by the PolicyBuilder. + Policy() = default; + + // Sends the policy over the IPC channel. + bool SendPolicy(Comms* comms) const; + + // Returns the policy, but modifies it according to FLAGS and internal + // requirements (message passing via Comms, Executor::WaitForExecve etc.). + std::vector GetPolicy() const; + + Namespace* GetNamespace() { return namespace_.get(); } + void SetNamespace(std::unique_ptr ns) { + namespace_ = std::move(ns); + } + + const std::vector* GetCapabilities() const { + return capabilities_.get(); + } + + // The Namespace object, defines ways of putting sandboxee into namespaces. + std::unique_ptr namespace_; + + // Gather stack traces on violations, signals, timeouts or when getting + // killed. See policybuilder.h for more information. + bool collect_stacktrace_on_violation_ = true; + bool collect_stacktrace_on_signal_ = true; + bool collect_stacktrace_on_timeout_ = true; + bool collect_stacktrace_on_kill_ = true; + + // The capabilities to keep in the sandboxee. + std::unique_ptr> capabilities_; + + // Optional pointer to a PolicyBuilder description pb object. + std::unique_ptr policy_builder_description_; + + // The policy set by the user. + std::vector user_policy_; + + // Get the default policy, which blocks certain dangerous syscalls and + // mismatched syscall tables. + std::vector GetDefaultPolicy() const; + // Get a policy which would allow the Monitor module to track all syscalls. + std::vector GetTrackingPolicy() const; + + friend class Monitor; + friend class PolicyBuilder; + friend class PolicyBuilderPeer; // For testing + friend class StackTracePeer; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_POLICY_H_ diff --git a/sandboxed_api/sandbox2/policy_test.cc b/sandboxed_api/sandbox2/policy_test.cc new file mode 100644 index 0000000..b9a0ef3 --- /dev/null +++ b/sandboxed_api/sandbox2/policy_test.cc @@ -0,0 +1,255 @@ +// 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/policy.h" + +#include +#include + +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/limits.h" +#include "sandboxed_api/sandbox2/policybuilder.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/sandbox2.h" +#include "sandboxed_api/sandbox2/syscall.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" + +using ::testing::Eq; + +namespace sandbox2 { +namespace { + +std::unique_ptr PolicyTestcasePolicy() { + return PolicyBuilder() + .AllowStaticStartup() + .AllowExit() + .AllowRead() + .AllowWrite() + .AllowSyscall(__NR_close) + .AllowSyscall(__NR_getppid) + .BlockSyscallWithErrno(__NR_open, ENOENT) + .BlockSyscallWithErrno(__NR_openat, ENOENT) + .BlockSyscallWithErrno(__NR_access, ENOENT) + .BlockSyscallWithErrno(__NR_prlimit64, EPERM) + .BuildOrDie(); +} + +#if defined(__x86_64__) +// Test that 32-bit syscalls from 64-bit are disallowed. +TEST(PolicyTest, AMD64Syscall32PolicyAllowed) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/policy"); + + std::vector args = {path, "1"}; + auto executor = absl::make_unique(path, args); + + auto policy = PolicyTestcasePolicy(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); + EXPECT_THAT(result.reason_code(), Eq(1)); // __NR_exit in 32-bit + EXPECT_THAT(result.GetSyscallArch(), Eq(Syscall::kX86_32)); +} + +// Test that 32-bit syscalls from 64-bit for FS checks are disallowed. +TEST(PolicyTest, AMD64Syscall32FsAllowed) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/policy"); + std::vector args = {path, "2"}; + auto executor = absl::make_unique(path, args); + + auto policy = PolicyTestcasePolicy(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); + EXPECT_THAT(result.reason_code(), + Eq(33)); // __NR_access in 32-bit + EXPECT_THAT(result.GetSyscallArch(), Eq(Syscall::kX86_32)); +} +#endif // defined(__x86_64__) + +// Test that ptrace(2) is disallowed. +TEST(PolicyTest, PtraceDisallowed) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/policy"); + std::vector args = {path, "3"}; + auto executor = absl::make_unique(path, args); + + auto policy = PolicyTestcasePolicy(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); + EXPECT_THAT(result.reason_code(), Eq(__NR_ptrace)); +} + +// Test that clone(2) with flag CLONE_UNTRACED is disallowed. +TEST(PolicyTest, CloneUntracedDisallowed) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/policy"); + std::vector args = {path, "4"}; + auto executor = absl::make_unique(path, args); + + auto policy = PolicyTestcasePolicy(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); + EXPECT_THAT(result.reason_code(), Eq(__NR_clone)); +} + +// Test that bpf(2) is disallowed. +TEST(PolicyTest, BpfDisallowed) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/policy"); + std::vector args = {path, "5"}; + auto executor = absl::make_unique(path, args); + + auto policy = PolicyTestcasePolicy(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); + EXPECT_THAT(result.reason_code(), Eq(__NR_bpf)); +} + +std::unique_ptr MinimalTestcasePolicy() { + return PolicyBuilder() + .AllowStaticStartup() + .AllowExit() + .BlockSyscallWithErrno(__NR_prlimit64, EPERM) + .BlockSyscallWithErrno(__NR_access, ENOENT) + .EnableNamespaces() + .BuildOrDie(); +} + +// Test that we can sandbox a minimal static binary returning 0. +// If this starts failing, it means something changed, maybe in the way we +// compile static binaries, and we need to update the policy just above. +TEST(MinimalTest, MinimalBinaryWorks) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/minimal"); + std::vector args = {path}; + auto executor = absl::make_unique(path, args); + + auto policy = MinimalTestcasePolicy(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::OK)); + EXPECT_THAT(result.reason_code(), Eq(EXIT_SUCCESS)); +} + +// Test that we can sandbox a minimal non-static binary returning 0. +TEST(MinimalTest, MinimalSharedBinaryWorks) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/minimal_dynamic"); + std::vector args = {path}; + auto executor = absl::make_unique(path, args); + + auto policy = PolicyBuilder() + .AllowDynamicStartup() + .AllowOpen() + .AllowExit() + .AllowMmap() + // New glibc accesses /etc/ld.so.preload + .BlockSyscallWithErrno(__NR_access, ENOENT) + .BlockSyscallWithErrno(__NR_prlimit64, EPERM) + .EnableNamespaces() + .AddLibrariesForBinary(path) + .BuildOrDie(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::OK)); + EXPECT_THAT(result.reason_code(), Eq(EXIT_SUCCESS)); +} + +// Test that the AllowSystemMalloc helper works as expected. +TEST(MallocTest, SystemMallocWorks) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/malloc_system"); + std::vector args = {path}; + auto executor = absl::make_unique(path, args); + + auto policy = PolicyBuilder() + .AllowStaticStartup() + .AllowSystemMalloc() + .AllowExit() + .EnableNamespaces() + .BlockSyscallWithErrno(__NR_prlimit64, EPERM) + .BlockSyscallWithErrno(__NR_access, ENOENT) + .BuildOrDie(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::OK)); + EXPECT_THAT(result.reason_code(), Eq(EXIT_SUCCESS)); +} + +// Complicated test to see that AddPolicyOnSyscalls work as +// expected. Specifically a worrisome corner-case would be that the logic was +// almost correct, but that the jump targets were off slightly. This uses the +// AddPolicyOnSyscall multiple times in a row to make any miscalculation +// unlikely to pass this check. +TEST(MultipleSyscalls, AddPolicyOnSyscallsWorks) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = + GetTestSourcePath("sandbox2/testcases/add_policy_on_syscalls"); + std::vector args = {path}; + auto executor = absl::make_unique(path, args); + + auto policy = + PolicyBuilder() + .BlockSyscallWithErrno(__NR_open, ENOENT) + .BlockSyscallWithErrno(__NR_openat, ENOENT) + .AllowStaticStartup() + .AllowTcMalloc() + .AllowExit() + .AddPolicyOnSyscalls( + {__NR_getuid, __NR_getgid, __NR_geteuid, __NR_getegid}, {ALLOW}) + .AddPolicyOnSyscalls({__NR_getresuid, __NR_getresgid}, {ERRNO(42)}) + .AddPolicyOnSyscalls({__NR_read, __NR_write}, {ERRNO(43)}) + .AddPolicyOnSyscall(__NR_umask, {DENY}) + .EnableNamespaces() + .BlockSyscallWithErrno(__NR_prlimit64, EPERM) + .BlockSyscallWithErrno(__NR_access, ENOENT) + .BuildOrDie(); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); + EXPECT_THAT(result.reason_code(), Eq(__NR_umask)); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc new file mode 100644 index 0000000..3ea38f7 --- /dev/null +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -0,0 +1,831 @@ +// 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/policybuilder.h" + +#include // For TCGETS +#if defined(__x86_64__) +#include +#endif +#if defined(__powerpc64__) +#include // On PPC, TCGETS macro needs termios +#endif +#include // For the fcntl flags +#include +#include // For GRND_NONBLOCK +#include // For mmap arguments +#include + +#include +#include +#include + +#include +#include "absl/strings/escaping.h" +#include "absl/strings/match.h" +#include "sandboxed_api/sandbox2/namespace.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/sandbox2/util/path.h" +#include "sandboxed_api/util/canonical_errors.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sandbox2 { +namespace { + +} // namespace + +PolicyBuilder& PolicyBuilder::AllowSyscall(unsigned int num) { + if (handled_syscalls_.insert(num).second) { + output_->user_policy_.insert(output_->user_policy_.end(), + {SYSCALL(num, ALLOW)}); + } + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowSyscalls(const std::vector& nums) { + for (auto num : nums) { + AllowSyscall(num); + } + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowSyscalls(SyscallInitializer nums) { + for (auto num : nums) { + AllowSyscall(num); + } + return *this; +} + +PolicyBuilder& PolicyBuilder::BlockSyscallWithErrno(unsigned int num, + int error) { + if (handled_syscalls_.insert(num).second) { + output_->user_policy_.insert(output_->user_policy_.end(), + {SYSCALL(num, ERRNO(error))}); + } + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowExit() { + return AllowSyscalls({__NR_exit, __NR_exit_group}); +} + +PolicyBuilder& PolicyBuilder::AllowScudoMalloc() { + AllowTime(); + AllowSyscalls({__NR_munmap, __NR_nanosleep}); + AllowFutexOp(FUTEX_WAKE); + AllowLimitedMadvise(); + AllowGetRandom(); + + return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { + return { + ARG_32(2), // prot + JEQ32(PROT_NONE, JUMP(&labels, prot_none)), + JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)), + + // PROT_READ | PROT_WRITE + ARG_32(3), // flags + JEQ32(MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, ALLOW), + JEQ32(MAP_PRIVATE | MAP_ANONYMOUS, ALLOW), + JUMP(&labels, mmap_end), + + // PROT_NONE + LABEL(&labels, prot_none), + ARG_32(3), // flags + JEQ32(MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, ALLOW), + + LABEL(&labels, mmap_end), + }; + }); +} + +PolicyBuilder& PolicyBuilder::AllowTcMalloc() { + AllowTime(); + AllowSyscalls({__NR_munmap, __NR_nanosleep, __NR_brk, __NR_mincore}); + AllowFutexOp(FUTEX_WAKE); + AllowLimitedMadvise(); + + AddPolicyOnSyscall(__NR_mprotect, { + ARG_32(2), + JEQ32(PROT_READ | PROT_WRITE, ALLOW), + JEQ32(PROT_NONE, ALLOW), + }); + + return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { + return { + ARG_32(2), // prot + JEQ32(PROT_NONE, JUMP(&labels, prot_none)), + JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)), + + // PROT_READ | PROT_WRITE + ARG_32(3), // flags + JNE32(MAP_ANONYMOUS | MAP_PRIVATE, JUMP(&labels, mmap_end)), + ALLOW, + + // PROT_NONE + LABEL(&labels, prot_none), + ARG_32(3), // flags + JEQ32(MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, ALLOW), + JEQ32(MAP_ANONYMOUS | MAP_PRIVATE, ALLOW), + + LABEL(&labels, mmap_end), + }; + }); +} + +PolicyBuilder& PolicyBuilder::AllowSystemMalloc() { + AllowSyscalls({__NR_munmap, __NR_brk}); + AddPolicyOnSyscall(__NR_mremap, { + ARG_32(3), + JEQ32(MREMAP_MAYMOVE, ALLOW), + }); + return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { + return { + ARG_32(2), // prot + JEQ32(PROT_NONE, JUMP(&labels, prot_none)), + JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)), + + // PROT_READ | PROT_WRITE + ARG_32(3), // flags + JEQ32(MAP_ANONYMOUS | MAP_PRIVATE, ALLOW), + + // PROT_NONE + LABEL(&labels, prot_none), + ARG_32(3), // flags + JEQ32(MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, ALLOW), + + LABEL(&labels, mmap_end), + }; + }); + + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowLimitedMadvise() { + return AddPolicyOnSyscall(__NR_madvise, { + ARG_32(2), + JEQ32(MADV_DONTNEED, ALLOW), + JEQ32(MADV_REMOVE, ALLOW), + }); +} + +PolicyBuilder& PolicyBuilder::AllowMmap() { + // Consistently with policy.cc, when mmap2 exists then mmap is denied (not + // allowed). +#ifdef __NR_mmap2 + return AllowSyscall(__NR_mmap2); +#else + return AllowSyscall(__NR_mmap); +#endif +} + +PolicyBuilder& PolicyBuilder::AllowOpen() { +#ifdef __NR_open + AllowSyscall(__NR_open); +#endif +#ifdef __NR_openat + AllowSyscall(__NR_openat); +#endif + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowStat() { +#ifdef __NR_fstat + AllowSyscall(__NR_fstat); +#endif +#ifdef __NR_fstat64 + AllowSyscall(__NR_fstat64); +#endif +#ifdef __NR_fstatat + AllowSyscall(__NR_fstatat); +#endif +#ifdef __NR_fstatat64 + AllowSyscall(__NR_fstatat64); +#endif +#ifdef __NR_lstat + AllowSyscall(__NR_lstat); +#endif +#ifdef __NR_lstat64 + AllowSyscall(__NR_lstat64); +#endif +#ifdef __NR_newfstatat + AllowSyscall(__NR_newfstatat); +#endif +#ifdef __NR_oldfstat + AllowSyscall(__NR_oldfstat); +#endif +#ifdef __NR_oldlstat + AllowSyscall(__NR_oldlstat); +#endif +#ifdef __NR_oldstat + AllowSyscall(__NR_oldstat); +#endif +#ifdef __NR_stat + AllowSyscall(__NR_stat); +#endif +#ifdef __NR_stat64 + AllowSyscall(__NR_stat64); +#endif +#ifdef __NR_statfs + AllowSyscall(__NR_statfs); +#endif +#ifdef __NR_statfs64 + AllowSyscall(__NR_statfs64); +#endif + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowRead() { + return AllowSyscalls({ + __NR_read, + __NR_readv, + __NR_preadv, + __NR_pread64, + }); +} + +PolicyBuilder& PolicyBuilder::AllowWrite() { + return AllowSyscalls({ + __NR_write, + __NR_writev, + __NR_pwritev, + __NR_pwrite64, + }); +} + +PolicyBuilder& PolicyBuilder::AllowReaddir() { + return AllowSyscalls({ +#ifdef __NR_getdents + __NR_getdents, +#endif +#ifdef __NR_getdents64 + __NR_getdents64, +#endif + }); +} + +PolicyBuilder& PolicyBuilder::AllowSafeFcntl() { + return AddPolicyOnSyscalls({__NR_fcntl, +#ifdef __NR_fcntl64 + __NR_fcntl64 +#endif + }, + { + ARG_32(1), + JEQ32(F_GETFD, ALLOW), + JEQ32(F_SETFD, ALLOW), + JEQ32(F_GETFL, ALLOW), + JEQ32(F_SETFL, ALLOW), + JEQ32(F_GETLK, ALLOW), + JEQ32(F_SETLK, ALLOW), + JEQ32(F_SETLKW, ALLOW), + JEQ32(F_DUPFD, ALLOW), + JEQ32(F_DUPFD_CLOEXEC, ALLOW), + }); +} + +PolicyBuilder& PolicyBuilder::AllowFork() { + return AllowSyscalls({ +#ifdef __NR_fork + __NR_fork, +#endif +#ifdef __NR_vfork + __NR_vfork, +#endif + __NR_clone}); +} + +PolicyBuilder& PolicyBuilder::AllowWait() { + return AllowSyscalls({ +#ifdef __NR_waitpid + __NR_waitpid, +#endif + __NR_wait4}); +} + +PolicyBuilder& PolicyBuilder::AllowHandleSignals() { + return AllowSyscalls({ + __NR_rt_sigaction, + __NR_rt_sigreturn, + __NR_rt_sigprocmask, +#ifdef __NR_signal + __NR_signal, +#endif +#ifdef __NR_sigaction + __NR_sigaction, +#endif +#ifdef __NR_sigreturn + __NR_sigreturn, +#endif +#ifdef __NR_sigprocmask + __NR_sigprocmask, +#endif + }); +} + +PolicyBuilder& PolicyBuilder::AllowTCGETS() { + return AddPolicyOnSyscall(__NR_ioctl, { + ARG_32(1), + JEQ32(TCGETS, ALLOW), + }); +} + +PolicyBuilder& PolicyBuilder::AllowTime() { + return AllowSyscalls({ +#ifdef __NR_time + __NR_time, +#endif + __NR_gettimeofday, __NR_clock_gettime}); +} + +PolicyBuilder& PolicyBuilder::AllowSleep() { + return AllowSyscalls({ + __NR_clock_nanosleep, + __NR_nanosleep, + }); +} + +PolicyBuilder& PolicyBuilder::AllowGetIDs() { + return AllowSyscalls({ + __NR_getuid, + __NR_geteuid, + __NR_getresuid, + __NR_getgid, + __NR_getegid, + __NR_getresgid, +#ifdef __NR_getuid32 + __NR_getuid32, + __NR_geteuid32, + __NR_getresuid32, + __NR_getgid32, + __NR_getegid32, + __NR_getresgid32, +#endif + __NR_getgroups, + }); +} + +PolicyBuilder& PolicyBuilder::AllowGetPIDs() { + return AllowSyscalls({ + __NR_getpid, + __NR_getppid, + __NR_gettid, + }); +} + +PolicyBuilder& PolicyBuilder::AllowGetRlimit() { + return AllowSyscalls({ + __NR_getrlimit, +#ifdef __NR_ugetrlimit + __NR_ugetrlimit, +#endif + }); +} + +PolicyBuilder& PolicyBuilder::AllowSetRlimit() { + return AllowSyscalls({ + __NR_setrlimit, +#ifdef __NR_usetrlimit + __NR_usetrlimit, +#endif + }); +} + +PolicyBuilder& PolicyBuilder::AllowGetRandom() { + return AddPolicyOnSyscall(__NR_getrandom, { + ARG_32(2), + JEQ32(0, ALLOW), + JEQ32(GRND_NONBLOCK, ALLOW), + }); +} + +PolicyBuilder& PolicyBuilder::AllowLogForwarding() { + AllowWrite(); + AllowSystemMalloc(); + AllowTcMalloc(); + + AllowSyscalls({// from logging code + __NR_clock_gettime, + // From comms + __NR_gettid, __NR_close}); + + // For LOG(FATAL) + return AddPolicyOnSyscall(__NR_kill, + [](bpf_labels& labels) -> std::vector { + return { + ARG_32(0), + JNE32(0, JUMP(&labels, pid_not_null)), + ARG_32(1), + JEQ32(SIGABRT, ALLOW), + LABEL(&labels, pid_not_null), + }; + }); +} + +PolicyBuilder& PolicyBuilder::AllowFutexOp(int op) { + return AddPolicyOnSyscall( + __NR_futex, { + ARG_32(1), + // a <- a & FUTEX_CMD_MASK + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, + static_cast(FUTEX_CMD_MASK)), + JEQ32(static_cast(op) & FUTEX_CMD_MASK, ALLOW), + }); +} + +PolicyBuilder& PolicyBuilder::AllowStaticStartup() { + AllowGetRlimit(); + AllowSyscalls({ + // These syscalls take a pointer, so no restriction. + __NR_uname, + __NR_brk, + __NR_set_tid_address, + + // This syscall takes a pointer and a length. + // We could restrict length, but it might change, so not worth it. + __NR_set_robust_list, + }); + + AllowFutexOp(FUTEX_WAIT_BITSET); + + AddPolicyOnSyscall(__NR_rt_sigaction, + { + ARG_32(0), + // This is real-time signals used internally by libc. + JEQ32(__SIGRTMIN + 0, ALLOW), + JEQ32(__SIGRTMIN + 1, ALLOW), + }); + + AddPolicyOnSyscall(__NR_rt_sigprocmask, { + ARG_32(0), + JEQ32(SIG_UNBLOCK, ALLOW), + }); + +#if defined(__x86_64__) + // The second argument is a pointer. + AddPolicyOnSyscall(__NR_arch_prctl, { + ARG_32(0), + JEQ32(ARCH_SET_FS, ALLOW), + }); +#endif + + BlockSyscallWithErrno(__NR_readlink, ENOENT); + + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowDynamicStartup() { + AllowRead(); + AllowStat(); + AllowSyscalls({__NR_lseek, __NR_close, __NR_munmap}); + AddPolicyOnSyscall(__NR_mprotect, { + ARG_32(2), + JEQ32(PROT_READ, ALLOW), + JEQ32(PROT_NONE, ALLOW), + JEQ32(PROT_READ | PROT_WRITE, ALLOW), + JEQ32(PROT_READ | PROT_EXEC, ALLOW), + }); + AllowStaticStartup(); + + return AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { + return { + ARG_32(2), // prot + JEQ32(PROT_READ | PROT_EXEC, JUMP(&labels, prot_exec)), + JEQ32(PROT_READ | PROT_WRITE, JUMP(&labels, prot_read_write)), + JNE32(PROT_READ, JUMP(&labels, mmap_end)), + + // PROT_READ + ARG_32(3), // flags + JEQ32(MAP_PRIVATE, ALLOW), + JUMP(&labels, mmap_end), + + // PROT_READ | PROT_WRITE + LABEL(&labels, prot_read_write), + ARG_32(3), // flags + JEQ32(MAP_FILE | MAP_PRIVATE | MAP_FIXED | MAP_DENYWRITE, ALLOW), + JEQ32(MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, ALLOW), + JEQ32(MAP_ANONYMOUS | MAP_PRIVATE, ALLOW), + JUMP(&labels, mmap_end), + + // PROT_READ | PROT_EXEC + LABEL(&labels, prot_exec), + ARG_32(3), // flags + JEQ32(MAP_FILE | MAP_PRIVATE | MAP_DENYWRITE, ALLOW), + + LABEL(&labels, mmap_end), + }; + }); +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall(unsigned int num, + BpfInitializer policy) { + return AddPolicyOnSyscalls({num}, policy); +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall( + unsigned int num, const std::vector& policy) { + return AddPolicyOnSyscalls({num}, policy); +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall(unsigned int num, BpfFunc f) { + return AddPolicyOnSyscalls({num}, f); +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnSyscalls( + SyscallInitializer nums, const std::vector& policy) { + auto resolved_policy = ResolveBpfFunc([nums, policy](bpf_labels& labels) + -> std::vector { + std::vector out; + out.reserve(nums.size() + policy.size()); + for (auto num : nums) { + out.insert(out.end(), {SYSCALL(num, JUMP(&labels, do_policy_l))}); + } + out.insert(out.end(), + {JUMP(&labels, dont_do_policy_l), LABEL(&labels, do_policy_l)}); + for (const auto& filter : policy) { + // Syscall arch is expected as TRACE value + if (filter.code == (BPF_RET | BPF_K) && + (filter.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE && + (filter.k & SECCOMP_RET_DATA) != Syscall::GetHostArch()) { + LOG(WARNING) << "SANDBOX2_TRACE should be used in policy instead of " + "TRACE(value)"; + out.push_back(SANDBOX2_TRACE); + } else { + out.push_back(filter); + } + } + out.push_back(LOAD_SYSCALL_NR); + out.insert(out.end(), {LABEL(&labels, dont_do_policy_l)}); + return out; + }); + // Pre-/Postcondition: Syscall number loaded into A register + output_->user_policy_.insert(output_->user_policy_.end(), + resolved_policy.begin(), resolved_policy.end()); + return *this; +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnSyscalls(SyscallInitializer nums, + BpfInitializer policy) { + std::vector policy_vector(policy); + return AddPolicyOnSyscalls(nums, policy_vector); +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnSyscalls(SyscallInitializer nums, + BpfFunc f) { + return AddPolicyOnSyscalls(nums, ResolveBpfFunc(f)); +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnMmap(BpfInitializer policy) { +#ifdef __NR_mmap2 + return AddPolicyOnSyscall(__NR_mmap2, policy); +#else + return AddPolicyOnSyscall(__NR_mmap, policy); +#endif +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnMmap( + const std::vector& policy) { +#ifdef __NR_mmap2 + return AddPolicyOnSyscall(__NR_mmap2, policy); +#else + return AddPolicyOnSyscall(__NR_mmap, policy); +#endif +} + +PolicyBuilder& PolicyBuilder::AddPolicyOnMmap(BpfFunc f) { +#ifdef __NR_mmap2 + return AddPolicyOnSyscall(__NR_mmap2, f); +#else + return AddPolicyOnSyscall(__NR_mmap, f); +#endif +} + +PolicyBuilder& PolicyBuilder::DangerDefaultAllowAll() { + output_->user_policy_.push_back(ALLOW); + return *this; +} + +::sapi::StatusOr PolicyBuilder::ValidateAbsolutePath( + absl::string_view path) { + if (!file::IsAbsolutePath(path)) { + return ::sapi::InvalidArgumentError( + absl::StrCat("Path is not absolute: '", path, "'")); + } + return ValidatePath(path); +} + +::sapi::StatusOr PolicyBuilder::ValidatePath(absl::string_view path) { + std::string fixed_path = file::CleanPath(path); + if (fixed_path != path) { + return ::sapi::InvalidArgumentError(absl::StrCat( + "Path was not normalized. '", path, "' != '", fixed_path, "'")); + } + return fixed_path; +} + +std::vector PolicyBuilder::ResolveBpfFunc(BpfFunc f) { + bpf_labels l = {0}; + + std::vector policy = f(l); + if (bpf_resolve_jumps(&l, policy.data(), policy.size()) != 0) { + SetError(::sapi::InternalError("Cannot resolve bpf jumps")); + } + + return policy; +} + +::sapi::StatusOr> PolicyBuilder::TryBuild() { + if (!last_status_.ok()) { + return last_status_; + } + + if (!output_) { + return ::sapi::FailedPreconditionError("Can only build policy once."); + } + + if (use_namespaces_) { + if (allow_unrestricted_networking_ && hostname_ != kDefaultHostname) { + return ::sapi::FailedPreconditionError( + "Cannot set hostname without network namespaces."); + } + output_->SetNamespace(absl::make_unique( + allow_unrestricted_networking_, std::move(mounts_), hostname_)); + } else { + // Not explicitly disabling them here as this is a technical limitation in + // our stack trace collection functionality. + LOG(WARNING) << "Using policy without namespaces, disabling stack traces on" + << " crash"; + } + + output_->collect_stacktrace_on_signal_ = collect_stacktrace_on_signal_; + output_->collect_stacktrace_on_violation_ = collect_stacktrace_on_violation_; + output_->collect_stacktrace_on_timeout_ = collect_stacktrace_on_timeout_; + output_->collect_stacktrace_on_kill_ = collect_stacktrace_on_kill_; + + auto pb_description = absl::make_unique(); + + StoreDescription(pb_description.get()); + output_->policy_builder_description_ = std::move(pb_description); + return std::move(output_); +} + +PolicyBuilder& PolicyBuilder::AddFile(absl::string_view path, bool is_ro) { + return AddFileAt(path, path, is_ro); +} + +PolicyBuilder& PolicyBuilder::SetError(const ::sapi::Status& status) { + LOG(ERROR) << status; + last_status_ = status; + return *this; +} + +PolicyBuilder& PolicyBuilder::AddFileAt(absl::string_view outside, + absl::string_view inside, bool is_ro) { + EnableNamespaces(); + + auto fixed_outside_or = ValidateAbsolutePath(outside); + if (!fixed_outside_or.ok()) { + SetError(fixed_outside_or.status()); + return *this; + } + auto fixed_outside = std::move(fixed_outside_or.ValueOrDie()); + + if (absl::StartsWith(fixed_outside, "/proc/self")) { + SetError(::sapi::InvalidArgumentError( + absl::StrCat("Cannot add /proc/self mounts, you need to mount the " + "whole /proc instead. You tried to mount ", + outside))); + return *this; + } + + auto status = mounts_.AddFileAt(fixed_outside, inside, is_ro); + if (!status.ok()) { + SetError(::sapi::InternalError(absl::StrCat("Could not add file ", outside, + " => ", inside, ": ", + status.message()))); + } + + return *this; +} + +PolicyBuilder& PolicyBuilder::AddLibrariesForBinary( + absl::string_view path, absl::string_view ld_library_path) { + EnableNamespaces(); + + auto fixed_path_or = ValidatePath(path); + if (!fixed_path_or.ok()) { + SetError(fixed_path_or.status()); + return *this; + } + auto fixed_path = std::move(fixed_path_or.ValueOrDie()); + + auto status = mounts_.AddMappingsForBinary(fixed_path, ld_library_path); + if (!status.ok()) { + SetError(::sapi::InternalError(absl::StrCat( + "Could not add libraries for ", fixed_path, ": ", status.message()))); + } + return *this; +} + +PolicyBuilder& PolicyBuilder::AddLibrariesForBinary( + int fd, absl::string_view ld_library_path) { + return AddLibrariesForBinary(absl::StrCat("/proc/self/fd/", fd), + ld_library_path); +} + +PolicyBuilder& PolicyBuilder::AddDirectory(absl::string_view path, bool is_ro) { + return AddDirectoryAt(path, path, is_ro); +} + +PolicyBuilder& PolicyBuilder::AddDirectoryAt(absl::string_view outside, + absl::string_view inside, + bool is_ro) { + EnableNamespaces(); + + auto fixed_outside_or = ValidateAbsolutePath(outside); + if (!fixed_outside_or.ok()) { + SetError(fixed_outside_or.status()); + return *this; + } + auto fixed_outside = std::move(fixed_outside_or.ValueOrDie()); + if (absl::StartsWith(fixed_outside, "/proc/self")) { + SetError(::sapi::InvalidArgumentError( + absl::StrCat("Cannot add /proc/self mounts, you need to mount the " + "whole /proc instead. You tried to mount ", + outside))); + return *this; + } + + auto status = mounts_.AddDirectoryAt(fixed_outside, inside, is_ro); + if (!status.ok()) { + SetError(::sapi::InternalError(absl::StrCat("Could not add directory ", + outside, " => ", inside, ": ", + status.message()))); + } + + return *this; +} + +PolicyBuilder& PolicyBuilder::AddTmpfs(absl::string_view inside, size_t sz) { + EnableNamespaces(); + + auto status = mounts_.AddTmpfs(inside, sz); + if (!status.ok()) { + SetError(::sapi::InternalError(absl::StrCat( + "Could not mount tmpfs ", inside, ": ", status.message()))); + } + + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowUnrestrictedNetworking() { + EnableNamespaces(); + allow_unrestricted_networking_ = true; + + return *this; +} + +PolicyBuilder& PolicyBuilder::SetHostname(absl::string_view hostname) { + EnableNamespaces(); + hostname_ = std::string(hostname); + + return *this; +} + +PolicyBuilder& PolicyBuilder::CollectStacktracesOnViolation(bool enable) { + collect_stacktrace_on_violation_ = enable; + return *this; +} + +PolicyBuilder& PolicyBuilder::CollectStacktracesOnSignal(bool enable) { + collect_stacktrace_on_signal_ = enable; + return *this; +} + +PolicyBuilder& PolicyBuilder::CollectStacktracesOnTimeout(bool enable) { + collect_stacktrace_on_timeout_ = enable; + return *this; +} + +PolicyBuilder& PolicyBuilder::CollectStacktracesOnKill(bool enable) { + collect_stacktrace_on_kill_ = enable; + return *this; +} + +void PolicyBuilder::StoreDescription(PolicyBuilderDescription* pb_description) { + for (const auto& handled_syscall : handled_syscalls_) { + pb_description->add_handled_syscalls(handled_syscall); + } +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h new file mode 100644 index 0000000..49e16be --- /dev/null +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -0,0 +1,518 @@ +// 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_POLICYBUILDER_H_ +#define SANDBOXED_API_SANDBOX2_POLICYBUILDER_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/macros.h" +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/mounts.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/stack-trace.h" +#include "sandboxed_api/util/statusor.h" + +struct bpf_labels; + +namespace sandbox2 { + +constexpr char kDefaultHostname[] = "sandbox2"; + +// PolicyBuilder is a helper class to simplify creation of policies. The builder +// uses fluent interface for convenience and increased readability of policies. +// +// To build a policy you simply create a new builder object, call methods on it +// specifying what you want and finally call BuildOrDie() to generate you +// policy. +// +// For instance this would generate a simple policy suitable for binaries doing +// only computations: +// +// std::unique_ptr policy = +// PolicyBuilder() +// .AllowRead() +// .AllowWrite() +// .AllowExit() +// .AllowSystemMalloc() +// .BuildOrDie(); +// +// Note that operations are executed in the order they are dictated, though in +// most cases this has no influence since the operations themselves commute. +// +// For instance these two policies are equivalent: +// +// auto policy = PolicyBuilder.AllowRead().AllowWrite().BuildOrDie(); +// auto policy = PolicyBuilder.AllowWrite().AllowRead().BuildOrDie(); +// +// While these two are not: +// +// auto policy = PolicyBuilder.AllowRead().BlockSyscallWithErrno(__NR_read, EIO) +// .BuildOrDie(); +// auto policy = PolicyBuilder.BlockSyscallWithErrno(__NR_read, EIO).AllowRead() +// .BuildOrDie(); +// +// In fact the first one is equivalent to: +// +// auto policy = PolicyBuilder.AllowRead().BuildOrDie(); +// +// If you dislike the chained style, is is also possible to write the first +// example as this: +// +// PolicyBuilder builder; +// builder.AllowRead(); +// builder.AllowWrite(); +// builder.AllowExit(); +// builder.AllowSystemMalloc(); +// auto policy = builder.BuildOrDie(); +// +// For a more complicated example, see examples/persistent/persistent_sandbox.cc +class PolicyBuilder final { + public: + using BpfInitializer = std::initializer_list; + using BpfFunc = const std::function(bpf_labels&)>&; + using SyscallInitializer = std::initializer_list; + + PolicyBuilder() : output_{new Policy()} {} + + PolicyBuilder(const PolicyBuilder&) = delete; + PolicyBuilder& operator=(const PolicyBuilder&) = delete; + + // Appends code to allow a specific syscall + PolicyBuilder& AllowSyscall(unsigned int num); + + // Appends code to allow a number of syscalls + PolicyBuilder& AllowSyscalls(const std::vector& nums); + PolicyBuilder& AllowSyscalls(SyscallInitializer nums); + + // Appends code to block a specific syscall while setting errno to the error + // given + PolicyBuilder& BlockSyscallWithErrno(unsigned int num, int error); + + // Appends code to allow exiting. + // Allows these syscalls: + // - exit + // - exit_group + PolicyBuilder& AllowExit(); + + // Appends code to allow the scudo version of malloc, free and + // friends. This should be used in conjunction with namespaces. If scudo + // options are passed to the sandboxee through an environment variable, access + // to "/proc/self/environ" will have to be allowed by the policy. + // + // Note: This function is tuned towards the secure scudo allocator. If you are + // using another implementation, this function might not be the most + // suitable. + PolicyBuilder& AllowScudoMalloc(); + + // Appends code to allow the system-allocator version of malloc, free and + // friends. + // + // Note: This function is tuned towards the malloc implementation in glibc. If + // you are using another implementation, this function might not be the + // most suitable. + PolicyBuilder& AllowSystemMalloc(); + + // Appends code to allow the tcmalloc version of malloc, free and + // friends. + PolicyBuilder& AllowTcMalloc(); + + // Appends code to allow mmap. Specifically this allows the mmap2 syscall on + // architectures where this syscalls exist and the mmap syscall on all other + // architectures. + // + // Note: while this function allows the calls, the default policy is run first + // and it has checks for dangerous flags which can create a violation. See + // sandbox2/policy.cc for more details. + PolicyBuilder& AllowMmap(); + + // Appends code to allow calling futex with the given operation. + PolicyBuilder& AllowFutexOp(int op); + + // Appends code to allow opening files or directories. Specifically it allows + // these sycalls: + // + // - open + // - openat + PolicyBuilder& AllowOpen(); + + // Appends code to allow calling stat, fstat and lstat. + // Allows these sycalls: + // - fstat + // - fstat64 + // - fstatat + // - fstatat64 + // - fstatfs + // - fstatfs64 + // - lstat + // - lstat64 + // - newfstatat + // - oldfstat + // - oldlstat + // - oldstat + // - stat + // - stat64 + // - statfs + // - statfs64 + // - ustat + PolicyBuilder& AllowStat(); + + // Appends code to the policy to allow reading from file descriptors. + // Allows these sycalls: + // - read + // - readv + // - preadv + // - pread64 + PolicyBuilder& AllowRead(); + + // Appends code to the policy to allow writing to file descriptors. + // Allows these sycalls: + // - write + // - writev + // - pwritev + // - pwrite64 + PolicyBuilder& AllowWrite(); + + // Appends code to allow reading directories. + // Allows these sycalls: + // - getdents + // - getdents64 + PolicyBuilder& AllowReaddir(); + + // Appends code to allow safe calls to fcntl. + // Allows these sycalls: + // - fcntl + // - fcntl64 (on architectures where it exists) + // + // The above are only allowed when the cmd is one of: + // F_GETFD, F_SETFD, F_GETFL, F_SETFL, F_GETLK, F_SETLKW, F_SETLK, + // F_DUPFD, F_DUPFD_CLOEXEC + PolicyBuilder& AllowSafeFcntl(); + + // Appends code to allow creating new processes. + // Allows these sycalls: + // - fork + // - vfork + // - clone + // + // Note: while this function allows the calls, the default policy is run first + // and it has checks for dangerous flags which can create a violation. See + // sandbox2/policy.cc for more details. + PolicyBuilder& AllowFork(); + + // Appends code to allow waiting for processes. + // Allows these sycalls: + // - waitpid (on architectures where it exists) + // - wait4 + PolicyBuilder& AllowWait(); + + // Appends code to allow setting up signal handlers, returning from them, etc. + // Allows these sycalls: + // - rt_sigaction + // - rt_sigreturn + // - rt_procmask + // - signal (on architectures where it exists) + // - sigaction (on architectures where it exists) + // - sigreturn (on architectures where it exists) + // - sigprocmask (on architectures where it exists) + PolicyBuilder& AllowHandleSignals(); + + // Appends code to allow doing the TCGETS ioctl. + // Allows these sycalls: + // - ioctl (when the first argument is TCGETS) + PolicyBuilder& AllowTCGETS(); + + // Appends code to allow to getting the current time. + // Allows these sycalls: + // - time + // - gettimeofday + // - clock_gettime + PolicyBuilder& AllowTime(); + + // Appends code to allow sleeping in the current thread. + // Allow these syscalls: + // - clock_nanosleep + // - nanosleep + PolicyBuilder& AllowSleep(); + + // Appends code to allow getting the uid, euid, gid, etc. + // - getuid + geteuid + getresuid + // - getgid + getegid + getresgid + // - getuid32 + geteuid32 + getresuid32 (on architectures where they exist) + // - getgid32 + getegid32 + getresgid32 (on architectures where they exist) + // - getgroups + PolicyBuilder& AllowGetIDs(); + + // Appends code to allow getting the pid, ppid and tid. + // Allows these syscalls: + // - getpid + // - getppid + // - gettid + PolicyBuilder& AllowGetPIDs(); + + // Appends code to allow getting the rlimits. + // Allows these sycalls: + // - getrlimit + // - ugetrlimit (on architectures where it exist) + PolicyBuilder& AllowGetRlimit(); + + // Appends code to allow setting the rlimits. + // Allows these sycalls: + // - setrlimit + // - usetrlimit (on architectures where it exist) + PolicyBuilder& AllowSetRlimit(); + + // Appends code to allow reading random bytes. + // Allows these sycalls: + // - getrandom (with no flags or GRND_NONBLOCK) + PolicyBuilder& AllowGetRandom(); + + // Enables syscalls required to use the logging support enabled via + // Client::SendLogsToSupervisor() + // Allows the following: + // - Writes + // - kill(0, SIGABRT) (for LOG(FATAL)) + // - clock_gettime + // - gettid + // - close + // + // If you don't use namespaces you should also add this to your policy: + // - policy->GetFs()->EnableSyscall(__NR_open); + // - policy->GetFs()->AddRegexpToGreyList("/usr/share/zoneinfo/.*"); + PolicyBuilder& AllowLogForwarding(); + + // Enables the syscalls necessary to start a statically linked binary + // + // NOTE: This will call BlockSyscallWithErrno(__NR_readlink, ENOENT). If you + // do not want readlink blocked, put a different call before this call. + // + // The current list of allowed syscalls are below. However you should *not* + // depend on the specifics, as these will change whenever the startup code + // changes. + // + // - uname, + // - brk, + // - set_tid_address, + // - set_robust_list, + // - futex(FUTEX_WAIT_BITSET, ...) + // - rt_sigaction(0x20, ...) + // - rt_sigaction(0x21, ...) + // - rt_sigprocmask(SIG_UNBLOCK, ...) + // - arch_prctl(ARCH_SET_FS) + // + // Additionally it will block calls to readlink. + PolicyBuilder& AllowStaticStartup(); + + // In addition to syscalls allowed by AllowStaticStartup, also allow reading, + // seeking, mmapping and closing files. It does not allow opening them, as + // the mechanism for doing so depends on whether GetFs-checks are used or not. + PolicyBuilder& AllowDynamicStartup(); + + // Appends a policy, which will be run on the specified syscall. + // This policy must be written without labels. If you need labels, use the + // next function. + PolicyBuilder& AddPolicyOnSyscall(unsigned int num, BpfInitializer policy); + PolicyBuilder& AddPolicyOnSyscall(unsigned int num, + const std::vector& policy); + + // Appends a policy, which will be run on the specified syscall. + // This policy may use labels. + // Example of how to use it: + // builder.AddPolicyOnSyscall( + // __NR_socket, [](bpf_labels& labels) -> std::vector { + // return { + // ARG(0), // domain is first argument of socket + // JEQ(AF_UNIX, JUMP(&labels, af_unix)), + // JEQ(AF_NETLINK, JUMP(&labels, af_netlink)), + // KILL, + // + // LABEL(&labels, af_unix), + // ARG(1), + // JEQ(SOCK_STREAM | SOCK_NONBLOCK, ALLOW), + // KILL, + // + // LABEL(&labels, af_netlink), + // ARG(2), + // JEQ(NETLINK_ROUTE, ALLOW), + // }; + // }); + PolicyBuilder& AddPolicyOnSyscall(unsigned int num, BpfFunc f); + + // Appends a policy, which will be run on the specified syscalls. + // This policy must be written without labels. + PolicyBuilder& AddPolicyOnSyscalls(SyscallInitializer nums, + BpfInitializer policy); + PolicyBuilder& AddPolicyOnSyscalls(SyscallInitializer nums, + const std::vector& policy); + + // Appends a policy, which will be run on the specified syscalls. + // This policy may use labels. + PolicyBuilder& AddPolicyOnSyscalls(SyscallInitializer nums, BpfFunc f); + + // Equivalent to AddPolicyOnSyscall(mmap_syscall_no, policy), where + // mmap_syscall_no is either __NR_mmap or __NR_mmap2. + PolicyBuilder& AddPolicyOnMmap(BpfInitializer policy); + PolicyBuilder& AddPolicyOnMmap(const std::vector& policy); + + // Equivalent to AddPolicyOnSyscall(mmap_syscall_no, f), where + // mmap_syscall_no is either __NR_mmap or __NR_mmap2. + PolicyBuilder& AddPolicyOnMmap(BpfFunc f); + + // Builds the policy returning a unique_ptr to it. This should only be called + // once. + ::sapi::StatusOr> TryBuild(); + + // Builds the policy returning a unique_ptr to it. This should only be called + // once. + // This function will abort if an error happened in any off the PolicyBuilder + // methods. + std::unique_ptr BuildOrDie() { return TryBuild().ValueOrDie(); } + + // Adds a bind-mount for a file from outside the namespace to inside. This + // will also create parent directories inside the namespace if needed. + // + // Calling these function will enable use of namespaces. + PolicyBuilder& AddFile(absl::string_view path, bool is_ro = true); + PolicyBuilder& AddFileAt(absl::string_view outside, absl::string_view inside, + bool is_ro = true); + + // Best-effort function that adds the libraries and linker required by a + // binary. + // + // This does not add the binary itself, only the libraries it depends on. + // + // This function should work correctly for most binaries, but you might need + // to tweak it in some cases. + // + // This function is safe even for untrusted/potentially malicious binaries. + // It adds libraries only from standard library dirs and ld_library_path. + // + // run `ldd` yourself and use AddFile or AddDirectory. + PolicyBuilder& AddLibrariesForBinary(absl::string_view path, + absl::string_view ld_library_path = {}); + + // Similar to AddLibrariesForBinary, but binary is specified with an open fd. + PolicyBuilder& AddLibrariesForBinary(int fd, + absl::string_view ld_library_path = {}); + + // Adds a bind-mount for a directory from outside the namespace to + // inside. This will also create parent directories inside the namespace if + // needed. + // + // Calling these function will enable use of namespaces. + PolicyBuilder& AddDirectory(absl::string_view path, bool is_ro = true); + PolicyBuilder& AddDirectoryAt(absl::string_view outside, + absl::string_view inside, bool is_ro = true); + + // Adds a tmpfs inside the namespace. This will also create parent + // directories inside the namespace if needed. + // + // Calling this function will enable use of namespaces. + PolicyBuilder& AddTmpfs(absl::string_view inside, + size_t sz = 4 << 20 /* 4MiB */); + + // Allows unrestricted access to the network by *not* creating a network + // namespace. Note that this only disables the network namespace. To actually + // allow networking, you would also need to allow networking syscalls. + // Calling this function will enable use of namespaces. + PolicyBuilder& AllowUnrestrictedNetworking(); + + // Enables the use of namespaces. + // + // Namespaces are automatically enabled when using namespace helper features + // (e.g. AddFile), therefore it is only necessary to explicitly enable + // namespaces when not using any other namespace helper feature. + PolicyBuilder& EnableNamespaces() { + use_namespaces_ = true; + return *this; + } + + // Set hostname in the network namespace instead of default "sandbox2". + // + // Calling this function will enable use of namespaces. + // It is an error to also call AllowUnrestrictedNetworking. + PolicyBuilder& SetHostname(absl::string_view hostname); + + // Enables/disables stack trace collection on violations. + PolicyBuilder& CollectStacktracesOnViolation(bool enable); + + // Enables/disables stack trace collection on signals (e.g. crashes / killed + // from a signal). + PolicyBuilder& CollectStacktracesOnSignal(bool enable); + + // Enables/disables stack trace collection on hitting a timeout. + PolicyBuilder& CollectStacktracesOnTimeout(bool enable); + + // Enables/disables stack trace collection on getting killed by the sandbox + // monitor / the user. + PolicyBuilder& CollectStacktracesOnKill(bool enable); + + // Appends an unconditional ALLOW action for all syscalls. + // Do not use in environment with untrusted code and/or data, ask + // sandbox-team@ first if unsure. + PolicyBuilder& DangerDefaultAllowAll(); + + private: + friend class PolicyBuilderPeer; // For testing + friend class StackTracePeer; + + // Allows a limited version of madvise + PolicyBuilder& AllowLimitedMadvise(); + + PolicyBuilder& SetMounts(Mounts mounts) { + mounts_ = std::move(mounts); + return *this; + } + + std::vector ResolveBpfFunc(BpfFunc f); + + static ::sapi::StatusOr ValidateAbsolutePath(absl::string_view path); + static ::sapi::StatusOr ValidatePath(absl::string_view path); + + void StoreDescription(PolicyBuilderDescription* pb_description); + + Mounts mounts_; + bool use_namespaces_ = false; + bool allow_unrestricted_networking_ = false; + std::string hostname_ = kDefaultHostname; + + bool collect_stacktrace_on_violation_ = true; + bool collect_stacktrace_on_signal_ = true; + bool collect_stacktrace_on_timeout_ = true; + bool collect_stacktrace_on_kill_ = false; + + // Seccomp fields + std::unique_ptr output_; + std::set handled_syscalls_; + + // Error handling + ::sapi::Status last_status_; + // This function returns a PolicyBuilder so that we can use it in the status + // macros + PolicyBuilder& SetError(const ::sapi::Status& status); +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_POLICYBUILDER_H_ diff --git a/sandboxed_api/sandbox2/policybuilder_test.cc b/sandboxed_api/sandbox2/policybuilder_test.cc new file mode 100644 index 0000000..e201ec6 --- /dev/null +++ b/sandboxed_api/sandbox2/policybuilder_test.cc @@ -0,0 +1,249 @@ +// 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/policybuilder.h" + +#include +#include + +#include + +#include +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/ipc.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/sandbox2.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/util/status_matchers.h" +#include "sandboxed_api/util/status.h" + +using ::testing::AllOf; +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::Gt; +using ::testing::HasSubstr; +using ::testing::Lt; +using ::testing::NotNull; +using ::testing::StartsWith; +using ::testing::StrEq; +using ::sapi::IsOk; +using ::sapi::StatusIs; + +namespace sandbox2 { + +class PolicyBuilderPeer { + public: + explicit PolicyBuilderPeer(PolicyBuilder* builder) : builder_{builder} {} + + int policy_size() const { return builder_->output_->user_policy_.size(); } + + static ::sapi::StatusOr ValidateAbsolutePath(absl::string_view path) { + return PolicyBuilder::ValidateAbsolutePath(path); + } + + private: + PolicyBuilder* builder_; +}; + +namespace { + +class PolicyBuilderTest : public testing::Test { + protected: + static std::string Run(std::vector args, bool network = false); +}; + +TEST_F(PolicyBuilderTest, Testpolicy_size) { + ssize_t last_size = 0; + PolicyBuilder builder; + PolicyBuilderPeer builder_peer{&builder}; + + auto assert_increased = [&last_size, &builder_peer]() { + ASSERT_THAT(last_size, Lt(builder_peer.policy_size())); + last_size = builder_peer.policy_size(); + }; + + auto assert_same = [&last_size, &builder_peer]() { + ASSERT_THAT(last_size, Eq(builder_peer.policy_size())); + }; + + // clang-format off + assert_same(); + + builder.AllowSyscall(__NR_chroot); assert_increased(); + builder.AllowSyscall(__NR_chroot); assert_same(); + builder.AllowSyscall(__NR_mmap); assert_increased(); + builder.AllowSyscall(__NR_mmap); assert_same(); + builder.AllowSyscall(__NR_chroot); assert_same(); + builder.AllowSyscall(__NR_chroot); assert_same(); + + builder.AllowSystemMalloc(); assert_increased(); + builder.AllowSyscall(__NR_munmap); assert_same(); + builder.BlockSyscallWithErrno(__NR_munmap, 1); assert_same(); + builder.BlockSyscallWithErrno(__NR_open, 1); + assert_increased(); + + builder.AllowTCGETS(); assert_increased(); + builder.AllowTCGETS(); assert_increased(); + builder.AllowTCGETS(); assert_increased(); + + builder.DangerDefaultAllowAll(); assert_increased(); + builder.DangerDefaultAllowAll(); assert_increased(); + builder.AddPolicyOnSyscall(__NR_fchmod, { ALLOW }); assert_increased(); + builder.AddPolicyOnSyscall(__NR_fchmod, { ALLOW }); assert_increased(); + + builder.AddPolicyOnSyscalls({ __NR_fchmod, __NR_chdir }, { ALLOW }); + assert_increased(); + builder.AddPolicyOnSyscalls({ __NR_fchmod, __NR_chdir }, { ALLOW }); + assert_increased(); + builder.AddPolicyOnSyscalls({ }, { ALLOW }); assert_increased(); + + // This might change in the future if we implement an optimization. + builder.AddPolicyOnSyscall(__NR_mmap, { ALLOW }); assert_increased(); + builder.AddPolicyOnSyscall(__NR_mmap, { ALLOW }); assert_increased(); + + // None of the namespace functions should alter the seccomp policy. + builder.AddFile("/usr/bin/find"); assert_same(); + builder.AddDirectory("/bin"); assert_same(); + builder.AddTmpfs("/tmp"); assert_same(); + builder.AllowUnrestrictedNetworking(); assert_same(); + // clang-format on +} + +TEST_F(PolicyBuilderTest, TestValidateAbsolutePath) { + for (auto const& bad_path : { + "..", + "a", + "a/b", + "a/b/c", + "/a/b/c/../d", + "/a/b/c/./d", + "/a/b/c//d", + "/a/b/c/d/", + "/a/bAAAAAAAAAAAAAAAAAAAAAA/c/d/", + }) { + auto path_or = PolicyBuilderPeer::ValidateAbsolutePath(bad_path); + EXPECT_THAT(path_or.status(), StatusIs(sapi::StatusCode::kInvalidArgument)); + } + + for (auto const& good_path : + {"/", "/a/b/c/d", "/a/b/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}) { + auto path_or = PolicyBuilderPeer::ValidateAbsolutePath(good_path); + EXPECT_THAT(path_or, IsOk()); + if (path_or.ok()) { + EXPECT_THAT(path_or.ValueOrDie(), StrEq(good_path)); + } + } +} + +std::string PolicyBuilderTest::Run(std::vector args, bool network) { + PolicyBuilder builder; + // Don't restrict the syscalls at all. + builder.DangerDefaultAllowAll(); + builder.AddLibrariesForBinary(args[0]); + if (network) { + builder.AllowUnrestrictedNetworking(); + } + + auto executor = absl::make_unique(args[0], args); +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) + executor->limits()->set_rlimit_as(RLIM64_INFINITY); +#endif + int fd1 = executor->ipc()->ReceiveFd(STDOUT_FILENO); + sandbox2::Sandbox2 s2(std::move(executor), builder.BuildOrDie()); + + s2.RunAsync(); + + char buf[4096]; + std::string output; + + while (true) { + int nbytes; + PCHECK((nbytes = read(fd1, buf, sizeof(buf))) >= 0); + + if (nbytes == 0) break; + output += std::string(buf, nbytes); + } + + auto result = s2.AwaitResult(); + EXPECT_EQ(result.final_status(), sandbox2::Result::OK); + return output; +} + +TEST_F(PolicyBuilderTest, TestCanOnlyBuildOnce) { + PolicyBuilder b; + ASSERT_THAT(b.BuildOrDie(), NotNull()); + ASSERT_DEATH(b.BuildOrDie(), "Can only build policy once"); +} + +TEST_F(PolicyBuilderTest, TestEcho) { + ASSERT_THAT(Run({"/bin/echo", "HELLO"}), StrEq("HELLO\n")); +} + +TEST_F(PolicyBuilderTest, TestInterfacesNoNetwork) { + auto lines = absl::StrSplit(Run({"/sbin/ip", "addr", "show", "up"}), '\n'); + + int count = 0; + for (auto const& line : lines) { + if (!line.empty() && !absl::StartsWith(line, " ")) { + count += 1; + } + } + + // Only loopback network interface 'lo'. + EXPECT_THAT(count, Eq(1)); +} + +TEST_F(PolicyBuilderTest, TestInterfacesNetwork) { + auto lines = + absl::StrSplit(Run({"/sbin/ip", "addr", "show", "up"}, true), '\n'); + + int count = 0; + for (auto const& line : lines) { + if (!line.empty() && !absl::StartsWith(line, " ")) { + count += 1; + } + } + + // Loopback network interface 'lo' and more. + EXPECT_THAT(count, Gt(1)); +} + +TEST_F(PolicyBuilderTest, TestUid) { + EXPECT_THAT(Run({"/usr/bin/id", "-u"}), StrEq("1000\n")); +} + +TEST_F(PolicyBuilderTest, TestGid) { + EXPECT_THAT(Run({"/usr/bin/id", "-g"}), StrEq("1000\n")); +} + +TEST_F(PolicyBuilderTest, TestOpenFds) { + SKIP_SANITIZERS_AND_COVERAGE; + + std::string sandboxee = GetTestSourcePath("sandbox2/testcases/print_fds"); + std::string expected = + absl::StrCat("0\n1\n2\n", sandbox2::Comms::kSandbox2ClientCommsFD, "\n"); + EXPECT_THAT(Run({sandboxee}), StrEq(expected)); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/regs.cc b/sandboxed_api/sandbox2/regs.cc new file mode 100644 index 0000000..bfde415 --- /dev/null +++ b/sandboxed_api/sandbox2/regs.cc @@ -0,0 +1,171 @@ +// 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::Regs class. + +#include "sandboxed_api/sandbox2/regs.h" + +#include // IWYU pragma: keep // used for NT_PRSTATUS inside an ifdef +#include +#include +#include // IWYU pragma: keep // used for iovec + +#include + +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/canonical_errors.h" + +namespace sandbox2 { + +::sapi::Status Regs::Fetch() { +#if defined(__powerpc64__) + iovec pt_iov = {&user_regs_, sizeof(user_regs_)}; + + if (ptrace(PTRACE_GETREGSET, pid_, NT_PRSTATUS, &pt_iov) == -1L) { + return ::sapi::InternalError(absl::StrCat( + "ptrace(PTRACE_GETREGSET, pid=", pid_, ") failed: ", StrError(errno))); + } + if (pt_iov.iov_len != sizeof(user_regs_)) { + return ::sapi::InternalError(absl::StrCat( + "ptrace(PTRACE_GETREGSET, pid=", pid_, + ") size returned: ", pt_iov.iov_len, + " different than sizeof(user_regs_): ", sizeof(user_regs_))); + } +#else + if (ptrace(PTRACE_GETREGS, pid_, 0, &user_regs_) == -1L) { + return ::sapi::InternalError(absl::StrCat( + "ptrace(PTRACE_GETREGS, pid=", pid_, ") failed: ", StrError(errno))); + } +#endif + return ::sapi::OkStatus(); +} + +::sapi::Status Regs::Store() { +#if defined(__powerpc64__) + iovec pt_iov = {&user_regs_, sizeof(user_regs_)}; + + if (ptrace(PTRACE_SETREGSET, pid_, NT_PRSTATUS, &pt_iov) == -1L) { + return ::sapi::InternalError(absl::StrCat( + "ptrace(PTRACE_SETREGSET, pid=", pid_, ") failed: ", StrError(errno))); + } +#else + if (ptrace(PTRACE_SETREGS, pid_, 0, &user_regs_) == -1) { + return ::sapi::InternalError(absl::StrCat( + "ptrace(PTRACE_SETREGS, pid=", pid_, ") failed: ", StrError(errno))); + } +#endif + return ::sapi::OkStatus(); +} + +::sapi::Status Regs::SkipSyscallReturnValue(uint64_t value) { +#if defined(__x86_64__) + user_regs_.orig_rax = -1; + user_regs_.rax = value; +#elif defined(__powerpc64__) + user_regs_.gpr[0] = -1; + user_regs_.gpr[3] = value; +#endif + return Store(); +} + +Syscall Regs::ToSyscall(Syscall::CpuArch syscall_arch) const { +#if defined(__x86_64__) + if (ABSL_PREDICT_TRUE(syscall_arch == Syscall::kX86_64)) { + auto syscall = user_regs_.orig_rax; + Syscall::Args args = {user_regs_.rdi, user_regs_.rsi, user_regs_.rdx, + user_regs_.r10, user_regs_.r8, user_regs_.r9}; + auto sp = user_regs_.rsp; + auto ip = user_regs_.rip; + return Syscall(syscall_arch, syscall, args, pid_, sp, ip); + } + if (syscall_arch == Syscall::kX86_32) { + auto syscall = user_regs_.orig_rax & 0xFFFFFFFF; + Syscall::Args args = { + user_regs_.rbx & 0xFFFFFFFF, user_regs_.rcx & 0xFFFFFFFF, + user_regs_.rdx & 0xFFFFFFFF, user_regs_.rsi & 0xFFFFFFFF, + user_regs_.rdi & 0xFFFFFFFF, user_regs_.rbp & 0xFFFFFFFF}; + auto sp = user_regs_.rsp & 0xFFFFFFFF; + auto ip = user_regs_.rip & 0xFFFFFFFF; + return Syscall(syscall_arch, syscall, args, pid_, sp, ip); + } +#elif defined(__powerpc64__) + if (ABSL_PREDICT_TRUE(syscall_arch == Syscall::kPPC_64)) { + auto syscall = user_regs_.gpr[0]; + Syscall::Args args = {user_regs_.orig_gpr3, user_regs_.gpr[4], + user_regs_.gpr[5], user_regs_.gpr[6], + user_regs_.gpr[7], user_regs_.gpr[8]}; + auto sp = user_regs_.gpr[1]; + auto ip = user_regs_.nip; + return Syscall(syscall_arch, syscall, args, pid_, sp, ip); + } +#endif + return Syscall(pid_); +} + +void Regs::StoreRegisterValuesInProtobuf(RegisterValues* values) const { +#if defined(__x86_64__) + RegisterX8664* regs = values->mutable_register_x86_64(); + regs->set_r15(user_regs_.r15); + regs->set_r14(user_regs_.r14); + regs->set_r13(user_regs_.r13); + regs->set_r12(user_regs_.r12); + regs->set_rbp(user_regs_.rbp); + regs->set_rbx(user_regs_.rbx); + regs->set_r11(user_regs_.r11); + regs->set_r10(user_regs_.r10); + regs->set_r9(user_regs_.r9); + regs->set_r8(user_regs_.r8); + regs->set_rax(user_regs_.rax); + regs->set_rcx(user_regs_.rcx); + regs->set_rdx(user_regs_.rdx); + regs->set_rsi(user_regs_.rsi); + regs->set_rdi(user_regs_.rdi); + regs->set_orig_rax(user_regs_.orig_rax); + regs->set_rip(user_regs_.rip); + regs->set_cs(user_regs_.cs); + regs->set_eflags(user_regs_.eflags); + regs->set_rsp(user_regs_.rsp); + regs->set_ss(user_regs_.ss); + regs->set_fs_base(user_regs_.fs_base); + regs->set_gs_base(user_regs_.gs_base); + regs->set_ds(user_regs_.ds); + regs->set_es(user_regs_.es); + regs->set_fs(user_regs_.fs); + regs->set_gs(user_regs_.gs); +#elif defined(__powerpc64__) + RegisterPowerpc64* regs = values->mutable_register_powerpc64(); + for (int i = 0; i < ABSL_ARRAYSIZE(user_regs_.gpr); ++i) { + regs->add_gpr(user_regs_.gpr[i]); + } + regs->set_nip(user_regs_.nip); + regs->set_msr(user_regs_.msr); + regs->set_orig_gpr3(user_regs_.orig_gpr3); + regs->set_ctr(user_regs_.ctr); + regs->set_link(user_regs_.link); + regs->set_xer(user_regs_.xer); + regs->set_ccr(user_regs_.ccr); + regs->set_softe(user_regs_.softe); + regs->set_trap(user_regs_.trap); + regs->set_dar(user_regs_.dar); + regs->set_dsisr(user_regs_.dsisr); + regs->set_result(user_regs_.result); + regs->set_zero0(user_regs_.zero0); + regs->set_zero1(user_regs_.zero1); + regs->set_zero2(user_regs_.zero2); + regs->set_zero3(user_regs_.zero3); +#endif +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/regs.h b/sandboxed_api/sandbox2/regs.h new file mode 100644 index 0000000..c43d302 --- /dev/null +++ b/sandboxed_api/sandbox2/regs.h @@ -0,0 +1,123 @@ +// 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 sandbox2::Regs class stores context of a process +// during ptrace stop events + +#ifndef SANDBOXED_API_SANDBOX2_REGS_H_ +#define SANDBOXED_API_SANDBOX2_REGS_H_ + +#include + +#include +#include + +#include "sandboxed_api/sandbox2/deathrattle_fatalmsg.pb.h" +#include "sandboxed_api/sandbox2/syscall.h" +#include "sandboxed_api/util/status.h" + +namespace sandbox2 { + +// Helper class to get and modify running processes registers. Uses ptrace and +// assumes the process is already attached. +class Regs { + public: +#if !defined(__x86_64__) && !defined(__powerpc64__) + static_assert(false, "No support for the current CPU architecture"); +#endif + + explicit Regs(pid_t pid) : pid_(pid) {} + + // Copies register values from the process + ::sapi::Status Fetch(); + + // Copies register values to the process + ::sapi::Status Store(); + + // Causes the process to skip current syscall and return given value instead + ::sapi::Status SkipSyscallReturnValue(uint64_t value); + + // Converts raw register values obtained on syscall entry to syscall info + Syscall ToSyscall(Syscall::CpuArch syscall_arch) const; + + pid_t pid() const { return pid_; } + + // Stores register values in a protobuf structure. + void StoreRegisterValuesInProtobuf(RegisterValues* values) const; + + private: + friend class StackTracePeer; + + struct PtraceRegisters { +#if defined(__x86_64__) + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t rbp; + uint64_t rbx; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; + uint64_t orig_rax; + uint64_t rip; + uint64_t cs; + uint64_t eflags; + uint64_t rsp; + uint64_t ss; + uint64_t fs_base; + uint64_t gs_base; + uint64_t ds; + uint64_t es; + uint64_t fs; + uint64_t gs; +#elif defined(__powerpc64__) + uint64_t gpr[32]; + uint64_t nip; + uint64_t msr; + uint64_t orig_gpr3; + uint64_t ctr; + uint64_t link; + uint64_t xer; + uint64_t ccr; + uint64_t softe; + uint64_t trap; + uint64_t dar; + uint64_t dsisr; + uint64_t result; + // elf.h's ELF_NGREG says it's 48 registers, so kernel fills it in with some + // zeroes. + uint64_t zero0; + uint64_t zero1; + uint64_t zero2; + uint64_t zero3; +#endif + }; + + // PID for which registers are fetched/stored + pid_t pid_ = 0; + + // Registers fetched with ptrace(PR_GETREGS/GETREGSET, pid). + PtraceRegisters user_regs_ = {}; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_REGS_H_ diff --git a/sandboxed_api/sandbox2/result.cc b/sandboxed_api/sandbox2/result.cc new file mode 100644 index 0000000..c82164b --- /dev/null +++ b/sandboxed_api/sandbox2/result.cc @@ -0,0 +1,188 @@ +// 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::Result class. + +#include "sandboxed_api/sandbox2/result.h" + +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/syscall.h" +#include "sandboxed_api/sandbox2/util.h" +#include "sandboxed_api/util/canonical_errors.h" + +namespace sandbox2 { + +Result& Result::operator=(const Result& other) { + final_status_ = other.final_status_; + reason_code_ = other.reason_code_; + stack_trace_ = other.stack_trace_; + if (other.regs_) { + regs_ = absl::make_unique(*other.regs_); + } else { + regs_.reset(nullptr); + } + if (other.syscall_) { + syscall_ = absl::make_unique(*other.syscall_); + } else { + syscall_.reset(nullptr); + } + prog_name_ = other.prog_name_; + proc_maps_ = other.proc_maps_; + rusage_monitor_ = other.rusage_monitor_; + return *this; +} + +::sapi::Status Result::ToStatus() const { + switch (final_status()) { + case OK: + if (reason_code() == 0) { + return ::sapi::OkStatus(); + } + break; + case TIMEOUT: + return ::sapi::DeadlineExceededError(ToString()); + default: + break; + } + return ::sapi::InternalError(ToString()); +} + +std::string Result::ToString() const { + std::string result; + switch (final_status()) { + case sandbox2::Result::UNSET: + result = absl::StrCat("UNSET - Code: ", reason_code()); + break; + case sandbox2::Result::OK: + result = absl::StrCat("OK - Exit code: ", reason_code()); + break; + case sandbox2::Result::SETUP_ERROR: + result = absl::StrCat( + "SETUP_ERROR - Code: ", + ReasonCodeEnumToString(static_cast(reason_code()))); + break; + case sandbox2::Result::VIOLATION: + result = absl::StrCat("SYSCALL VIOLATION - Violating Syscall ", + Syscall::GetArchDescription(GetSyscallArch()), "[", + reason_code(), "/", + Syscall(GetSyscallArch(), reason_code()).GetName(), + "] Stack: ", GetStackTrace()); + break; + case sandbox2::Result::SIGNALED: + result = absl::StrCat("Process terminated with a SIGNAL - Signal: ", + util::GetSignalName(reason_code()), + " Stack: ", GetStackTrace()); + break; + case sandbox2::Result::TIMEOUT: + result = absl::StrCat("Process TIMEOUT - Code: ", reason_code(), + " Stack: ", GetStackTrace()); + break; + case sandbox2::Result::EXTERNAL_KILL: + result = absl::StrCat("Process killed by user - Code: ", reason_code(), + " Stack: ", GetStackTrace()); + break; + case sandbox2::Result::INTERNAL_ERROR: + result = absl::StrCat( + "INTERNAL_ERROR - Code: ", + ReasonCodeEnumToString(static_cast(reason_code()))); + break; + default: + result = + absl::StrCat("(", final_status(), ") Code: ", reason_code()); + } +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) + absl::StrAppend(&result, + " - Warning: this executor is built with ASAN, " + "MSAN or TSAN, chances are the sandboxee too, which is " + "incompatible with sandboxing."); +#else + if ( + getenv("COVERAGE") != nullptr) { + absl::StrAppend(&result, + " - Warning: this executor is built with coverage " + "enabled, chances are the sandboxee too, which is " + "incompatible with sandboxing."); + } +#endif + return result; +} + +std::string Result::StatusEnumToString(StatusEnum value) { + switch (value) { + case sandbox2::Result::UNSET: + return "UNSET"; + case sandbox2::Result::OK: + return "OK"; + case sandbox2::Result::SETUP_ERROR: + return "SETUP_ERROR"; + case sandbox2::Result::VIOLATION: + return "VIOLATION"; + case sandbox2::Result::SIGNALED: + return "SIGNALED"; + case sandbox2::Result::TIMEOUT: + return "TIMEOUT"; + case sandbox2::Result::EXTERNAL_KILL: + return "EXTERNAL_KILL"; + case sandbox2::Result::INTERNAL_ERROR: + return "INTERNAL_ERROR"; + } + return "UNKNOWN"; +} + +std::string Result::ReasonCodeEnumToString(ReasonCodeEnum value) { + switch (value) { + case sandbox2::Result::UNSUPPORTED_ARCH: + return "UNSUPPORTED_ARCH"; + case sandbox2::Result::FAILED_TIMERS: + return "FAILED_TIMERS"; + case sandbox2::Result::FAILED_SIGNALS: + return "FAILED_SIGNALS"; + case sandbox2::Result::FAILED_SUBPROCESS: + return "FAILED_SUBPROCESS"; + case sandbox2::Result::FAILED_NOTIFY: + return "FAILED_NOTIFY"; + case sandbox2::Result::FAILED_CONNECTION: + return "FAILED_CONNECTION"; + case sandbox2::Result::FAILED_WAIT: + return "FAILED_WAIT"; + case sandbox2::Result::FAILED_NAMESPACES: + return "FAILED_NAMESPACES"; + case sandbox2::Result::FAILED_PTRACE: + return "FAILED_PTRACE"; + case sandbox2::Result::FAILED_IPC: + return "FAILED_IPC"; + case sandbox2::Result::FAILED_LIMITS: + return "FAILED_LIMITS"; + case sandbox2::Result::FAILED_CWD: + return "FAILED_CWD"; + case sandbox2::Result::FAILED_POLICY: + return "FAILED_POLICY"; + case sandbox2::Result::FAILED_STORE: + return "FAILED_STORE"; + case sandbox2::Result::FAILED_FETCH: + return "FAILED_FETCH"; + case sandbox2::Result::FAILED_GETEVENT: + return "FAILED_GETEVENT"; + case sandbox2::Result::FAILED_MONITOR: + return "FAILED_MONITOR"; + case sandbox2::Result::VIOLATION_SYSCALL: + return "VIOLATION_SYSCALL"; + case sandbox2::Result::VIOLATION_ARCH: + return "VIOLATION_ARCH"; + } + return absl::StrCat("UNKNOWN: ", value); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/result.h b/sandboxed_api/sandbox2/result.h new file mode 100644 index 0000000..d9f6516 --- /dev/null +++ b/sandboxed_api/sandbox2/result.h @@ -0,0 +1,192 @@ +// 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 sandbox2::Result class which will in future handle both +// exit status of the sandboxed process, and possible results returned from it. + +#ifndef SANDBOXED_API_SANDBOX2_RESULT_H_ +#define SANDBOXED_API_SANDBOX2_RESULT_H_ + +#include +#include + +#include +#include +#include +#include + +#include "absl/memory/memory.h" +#include "sandboxed_api/sandbox2/regs.h" +#include "sandboxed_api/sandbox2/syscall.h" +#include "sandboxed_api/util/status.h" + +namespace sandbox2 { + +class Result { + public: + // Final execution status. + enum StatusEnum { + // Not set yet + UNSET = 0, + // OK + OK, + // Sandbox initialization failure + SETUP_ERROR, + // Syscall violation + VIOLATION, + // Process terminated with a signal + SIGNALED, + // Process terminated with a timeout + TIMEOUT, + // Killed externally by user + EXTERNAL_KILL, + // Most likely ptrace() API failed + INTERNAL_ERROR, + }; + + // Detailed reason codes + enum ReasonCodeEnum { + // Codes used by status=`SETUP_ERROR`: + UNSUPPORTED_ARCH = 0, + FAILED_TIMERS, + FAILED_SIGNALS, + FAILED_SUBPROCESS, + FAILED_NOTIFY, + FAILED_CONNECTION, + FAILED_WAIT, + FAILED_NAMESPACES, + FAILED_PTRACE, + FAILED_IPC, + FAILED_LIMITS, + FAILED_CWD, + FAILED_POLICY, + + // Codes used by status=`INTERNAL_ERROR`: + FAILED_STORE, + FAILED_FETCH, + FAILED_GETEVENT, + FAILED_MONITOR, + + // TODO(wiktorg) not used currently (syscall number stored insted) - need to + // fix clients first + // Codes used by status=`VIOLATION`: + VIOLATION_SYSCALL, + VIOLATION_ARCH, + }; + + Result() = default; + Result(const Result& other) { *this = other; } + Result& operator=(const Result& other); + Result(Result&&) = default; + Result& operator=(Result&&) = default; + + void IgnoreResult() const {} + + // Setters/getters for the final status/code value. + void SetExitStatusCode(StatusEnum final_status, uintptr_t reason_code) { + // Don't overwrite exit status codes. + if (final_status_ != UNSET) { + return; + } + final_status_ = final_status; + reason_code_ = reason_code; + } + + // Sets the stack trace. + // The stacktrace must be sometimes fetched before SetExitStatusCode is + // called, because after WIFEXITED() or WIFSIGNALED() the process is just a + // zombie. + void SetStackTrace(const std::string& stack_trace) { stack_trace_ = stack_trace; } + + void LoadRegs(pid_t pid) { + auto regs = absl::make_unique(pid); + if (regs->Fetch().ok()) { + SetRegs(std::move(regs)); + } + } + + void SetRegs(std::unique_ptr regs) { regs_ = std::move(regs); } + + void SetSyscall(std::unique_ptr syscall) { + syscall_ = std::move(syscall); + } + + StatusEnum final_status() const { return final_status_; } + uintptr_t reason_code() const { return reason_code_; } + + // Returns the current syscall architecture. + // Client architecture when final_status_ == VIOLATION, might be different + // from the host architecture (32-bit vs 64-bit syscalls). + Syscall::CpuArch GetSyscallArch() const { + return syscall_ ? syscall_->arch() : Syscall::kUnknown; + } + + const std::string& GetStackTrace() const { return stack_trace_; } + + const Regs* GetRegs() const { return regs_.get(); } + + const Syscall* GetSyscall() const { return syscall_.get(); } + + const std::string& GetProgName() const { return prog_name_; } + + void SetProgName(const std::string& name) { prog_name_ = name; } + + const std::string& GetProcMaps() const { return proc_maps_; } + + void SetProcMaps(const std::string& proc_maps) { proc_maps_ = proc_maps; } + + // Converts this result to a ::sapi::Status object. The status will only be + // OK if the sandbox process exited normally with an exit code of 0. + ::sapi::Status ToStatus() const; + + // Returns a descriptive std::string for final result. + std::string ToString() const; + + // Converts StatusEnum to a std::string. + static std::string StatusEnumToString(StatusEnum value); + + // Converts ReasonCodeEnum to a std::string. + static std::string ReasonCodeEnumToString(ReasonCodeEnum value); + + rusage* GetRUsageMonitor() { return &rusage_monitor_; } + + private: + // Final execution status - see 'StatusEnum' for details. + StatusEnum final_status_ = UNSET; + // Termination cause: + // a). process exit value if final_status_ == OK, + // b). terminating signal if final_status_ == SIGNALED, + // c). violating syscall if final_status_ == VIOLATION, + // unspecified for the rest of status_ values. + uintptr_t reason_code_ = 0; + // Might contain stack-trace of the process, especially if it failed with + // syscall violation, or was terminated by a signal. + std::string stack_trace_; + // Might contain the register values of the process, similar to the stack. + // trace + std::unique_ptr regs_; + // Might contain violating syscall information + std::unique_ptr syscall_; + // Name of the process (as it can not be accessed anymore after termination). + std::string prog_name_; + // /proc/pid/maps of the main process. + std::string proc_maps_; + // Final resource usage as defined in (man getrusage), for + // the Monitor thread. + rusage rusage_monitor_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_RESULT_H_ diff --git a/sandboxed_api/sandbox2/sandbox2.cc b/sandboxed_api/sandbox2/sandbox2.cc new file mode 100644 index 0000000..15b98ea --- /dev/null +++ b/sandboxed_api/sandbox2/sandbox2.cc @@ -0,0 +1,129 @@ +// 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::Sandbox class. + +#include "sandboxed_api/sandbox2/sandbox2.h" + +#include +#include +#include + +#include "absl/memory/memory.h" +#include "absl/synchronization/blocking_counter.h" +#include "sandboxed_api/sandbox2/monitor.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/util/canonical_errors.h" + +namespace sandbox2 { + +Sandbox2::~Sandbox2() { + if (monitor_thread_ && monitor_thread_->joinable()) { + monitor_thread_->join(); + } +} + +sapi::StatusOr Sandbox2::AwaitResultWithTimeout( + absl::Duration timeout) { + CHECK(monitor_ != nullptr) << "Sandbox was not launched yet"; + CHECK(monitor_thread_ != nullptr) << "Sandbox was already waited on"; + + absl::MutexLock lock(&monitor_->done_mutex_); + auto done = monitor_->done_mutex_.AwaitWithTimeout( + absl::Condition(monitor_.get(), &Monitor::IsDone), timeout); + if (!done) { + return ::sapi::DeadlineExceededError( + "Sandbox did not finish within timeout"); + } + monitor_thread_->join(); + + CHECK(IsTerminated()) << "Monitor did not terminate"; + + // Reset the Monitor Thread object to its initial state, as to mark that this + // object cannot be used anymore to control behavior of the sandboxee (e.g. + // via signals). + monitor_thread_.reset(nullptr); + + VLOG(1) << "Final execution status: " << monitor_->result_.ToString(); + CHECK(monitor_->result_.final_status() != Result::UNSET); + return std::move(monitor_->result_); +} + +Result Sandbox2::AwaitResult() { + return AwaitResultWithTimeout(absl::InfiniteDuration()).ValueOrDie(); +} + +bool Sandbox2::RunAsync() { + Launch(); + + // If the sandboxee setup failed we return 'false' here. + if (monitor_->IsDone() && + monitor_->result_.final_status() == Result::SETUP_ERROR) { + return false; + } + return true; +} + +void Sandbox2::Kill() { + CHECK(monitor_ != nullptr) << "Sandbox was not launched yet"; + + // The sandboxee (and its monitoring thread) are already gone. Ignore the + // request instead of panic'ing in such case. + if (monitor_thread_ == nullptr) { + return; + } + pthread_kill(monitor_thread_->native_handle(), Monitor::kExternalKillSignal); +} + +void Sandbox2::DumpStackTrace() { + CHECK(monitor_ != nullptr) << "Sandbox was not launched yet"; + + if (monitor_thread_ == nullptr) { + return; + } + pthread_kill(monitor_thread_->native_handle(), Monitor::kDumpStackSignal); +} + +bool Sandbox2::IsTerminated() const { + CHECK(monitor_ != nullptr) << "Sandbox was not launched yet"; + + return monitor_->IsDone(); +} + +void Sandbox2::SetWallTimeLimit(time_t limit) const { + CHECK(monitor_ != nullptr) << "Sandbox was not launched yet"; + + if (monitor_thread_ == nullptr) { + return; + } + + union sigval v; + v.sival_int = static_cast(limit); + pthread_sigqueue(monitor_thread_->native_handle(), Monitor::kTimerSetSignal, + v); +} + +void Sandbox2::Launch() { + monitor_ = + absl::make_unique(executor_.get(), policy_.get(), notify_.get()); + monitor_thread_ = + absl::make_unique(&Monitor::Run, monitor_.get()); + + // Wait for the Monitor to set-up the sandboxee correctly (or fail while + // doing that). From here on, it is safe to use the IPC object for + // non-sandbox-related data exchange. + monitor_->setup_counter_->Wait(); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/sandbox2.h b/sandboxed_api/sandbox2/sandbox2.h new file mode 100644 index 0000000..ce8aa48 --- /dev/null +++ b/sandboxed_api/sandbox2/sandbox2.h @@ -0,0 +1,134 @@ +// 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::Sandbox object is the central object of the Sandbox2. +// It handles sandboxed jobs. + +#ifndef SANDBOXED_API_SANDBOX2_SANDBOX2_H_ +#define SANDBOXED_API_SANDBOX2_SANDBOX2_H_ + +#include +#include +#include // NOLINT(build/c++11) +#include + +#include +#include "absl/base/macros.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/monitor.h" +#include "sandboxed_api/sandbox2/notify.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/util/statusor.h" + +namespace sandbox2 { + +class Sandbox2 final { + public: + Sandbox2(std::unique_ptr executor, std::unique_ptr policy) + : Sandbox2(std::move(executor), std::move(policy), /*notify=*/nullptr) {} + + Sandbox2(std::unique_ptr executor, std::unique_ptr policy, + std::unique_ptr notify) + : executor_(std::move(executor)), + policy_(std::move(policy)), + notify_(std::move(notify)) { + CHECK(executor_ != nullptr); + CHECK(policy_ != nullptr); + if (notify_ == nullptr) { + notify_ = absl::make_unique(); + } + } + + ~Sandbox2(); + + Sandbox2(const Sandbox2&) = delete; + Sandbox2& operator=(const Sandbox2&) = delete; + + // Runs the sandbox, blocking until there is a result. + ABSL_MUST_USE_RESULT Result Run() { + RunAsync(); + return AwaitResult(); + } + + // Runs asynchronously. The return value indicates whether the sandboxee + // set-up process succeeded + // Even if set-up fails AwaitResult can still used to get a more specific + // failure reason. + bool RunAsync(); + // Waits for sandbox execution to finish and returns the execution result. + ABSL_MUST_USE_RESULT Result AwaitResult(); + + // Waits for sandbox execution to finish within the timeout. + // Returns execution result or a DeadlineExceededError if the sandboxee does + // not finish in time. + ::sapi::StatusOr AwaitResultWithTimeout(absl::Duration timeout); + + // Requests termination of the sandboxee. + // Sandbox should still waited with AwaitResult(), as it may finish for other + // reason before the request is handled. + void Kill(); + + // Dumps the main sandboxed process's stack trace to log. + void DumpStackTrace(); + + // Returns whether sandboxing task has ended. + bool IsTerminated() const; + + // Sets a wall time limit on a running sandboxee, 0 to disarm. + // Limit is a timeout duration (e.g. 10 secs) not a deadline (e.g. 12:00). + // This can be useful in a persistent sandbox scenario, to impose a deadline + // for responses after each request and reset the deadline in between. + // Sandboxed API can be used to implement persistent sandboxes. + void SetWallTimeLimit(time_t limit) const; + + // Gets the pid inside the executor. + pid_t GetPid() { + if (monitor_ != nullptr) { + return monitor_->pid_; + } + return -1; + } + + // Gets the comms inside the executor. + Comms* comms() { + return executor_ != nullptr ? executor_->ipc()->comms() : nullptr; + } + + private: + // Launches the Monitor. + void Launch(); + + // Executor set by user - owned by Sandbox2. + std::unique_ptr executor_; + + // Seccomp policy set by the user - owned by Sandbox2. + std::unique_ptr policy_; + + // Notify object - owned by Sandbox2. + std::unique_ptr notify_; + + // Monitor object - owned by Sandbox2. + std::unique_ptr monitor_; + + // Monitor thread object - owned by Sandbox2. + std::unique_ptr monitor_thread_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_SANDBOX2_H_ diff --git a/sandboxed_api/sandbox2/sandbox2_test.cc b/sandboxed_api/sandbox2/sandbox2_test.cc new file mode 100644 index 0000000..d2f5808 --- /dev/null +++ b/sandboxed_api/sandbox2/sandbox2_test.cc @@ -0,0 +1,200 @@ +// 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/sandbox2.h" + +#include +#include + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/policybuilder.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/util/status_matchers.h" + +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::IsEmpty; + +namespace sandbox2 { +namespace { + +// Test that aborting inside a sandbox with all userspace core dumping +// disabled reports the signal. +TEST(SandboxCoreDumpTest, AbortWithoutCoreDumpReturnsSignaled) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/abort"); + std::vector args = { + path, + }; + auto executor = absl::make_unique(path, args); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + + ASSERT_EQ(result.final_status(), Result::SIGNALED); + EXPECT_EQ(result.reason_code(), SIGABRT); +} + +// Test that with TSYNC we are able to sandbox when multithreaded and with no +// memory checks. If TSYNC is not supported, then no. +TEST(TsyncTest, TsyncNoMemoryChecks) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/tsync"); + std::vector args = {path}; + auto executor = absl::make_unique(path, args); + executor->set_enable_sandbox_before_exec(false); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + + // With TSYNC, SandboxMeHere should be able to sandbox when multithreaded. + ASSERT_EQ(result.final_status(), Result::OK); + ASSERT_EQ(result.reason_code(), 0); +} + +// Tests whether Executor(fd, args, envp) constructor works as +// expected. +TEST(ExecutorTest, ExecutorFdConstructor) { + SKIP_SANITIZERS_AND_COVERAGE; + + const std::string path = GetTestSourcePath("sandbox2/testcases/minimal"); + int fd = open(path.c_str(), O_RDONLY); + ASSERT_NE(fd, -1); + + std::vector args = {absl::StrCat("FD:", fd)}; + std::vector envs; + auto executor = absl::make_unique(fd, args, envs); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + Sandbox2 sandbox(std::move(executor), std::move(policy)); + auto result = sandbox.Run(); + + ASSERT_EQ(result.final_status(), Result::OK); +} + +// Tests that we return the correct state when the sandboxee was killed by an +// external signal. Also make sure that we do not have the stack trace. +TEST(RunAsyncTest, SandboxeeExternalKill) { + const std::string path = GetTestSourcePath("sandbox2/testcases/sleep"); + + std::vector args = {path}; + std::vector envs; + auto executor = absl::make_unique(path, args, envs); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .EnableNamespaces() + .TryBuild()); + Sandbox2 sandbox(std::move(executor), std::move(policy)); + ASSERT_TRUE(sandbox.RunAsync()); + sleep(1); + sandbox.Kill(); + auto result = sandbox.AwaitResult(); + EXPECT_EQ(result.final_status(), Result::EXTERNAL_KILL); + + EXPECT_THAT(result.GetStackTrace(), IsEmpty()); +} + +// Tests that we return the correct state when the sandboxee timed out. +TEST(RunAsyncTest, SandboxeeTimeoutWithStacktraces) { + const std::string path = GetTestSourcePath("sandbox2/testcases/sleep"); + + std::vector args = {path}; + std::vector envs; + auto executor = absl::make_unique(path, args, envs); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .EnableNamespaces() + .TryBuild()); + Sandbox2 sandbox(std::move(executor), std::move(policy)); + ASSERT_TRUE(sandbox.RunAsync()); + sandbox.SetWallTimeLimit(1); + auto result = sandbox.AwaitResult(); + EXPECT_EQ(result.final_status(), Result::TIMEOUT); + EXPECT_THAT(result.GetStackTrace(), HasSubstr("sleep")); +} + +// Tests that we do not collect stack traces if it was disabled (signaled). +TEST(RunAsyncTest, SandboxeeTimeoutDisabledStacktraces) { + const std::string path = GetTestSourcePath("sandbox2/testcases/sleep"); + + std::vector args = {path}; + std::vector envs; + auto executor = absl::make_unique(path, args, envs); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .EnableNamespaces() + .CollectStacktracesOnTimeout(false) + .TryBuild()); + Sandbox2 sandbox(std::move(executor), std::move(policy)); + ASSERT_TRUE(sandbox.RunAsync()); + sandbox.SetWallTimeLimit(1); + auto result = sandbox.AwaitResult(); + EXPECT_EQ(result.final_status(), Result::TIMEOUT); + EXPECT_THAT(result.GetStackTrace(), IsEmpty()); +} + +// Tests that we do not collect stack traces if it was disabled (violation). +TEST(RunAsyncTest, SandboxeeViolationDisabledStacktraces) { + const std::string path = GetTestSourcePath("sandbox2/testcases/sleep"); + + std::vector args = {path}; + std::vector envs; + auto executor = absl::make_unique(path, args, envs); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, + PolicyBuilder() + // Don't allow anything - Make sure that we'll crash. + .EnableNamespaces() + .CollectStacktracesOnViolation(false) + .TryBuild()); + Sandbox2 sandbox(std::move(executor), std::move(policy)); + ASSERT_TRUE(sandbox.RunAsync()); + auto result = sandbox.AwaitResult(); + EXPECT_EQ(result.final_status(), Result::VIOLATION); + EXPECT_THAT(result.GetStackTrace(), IsEmpty()); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/sanitizer.cc b/sandboxed_api/sandbox2/sanitizer.cc new file mode 100644 index 0000000..5ff6d34 --- /dev/null +++ b/sandboxed_api/sandbox2/sanitizer.cc @@ -0,0 +1,224 @@ +// 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::sanitizer namespace. + +#include "sandboxed_api/sandbox2/sanitizer.h" + +#if defined(THREAD_SANITIZER) +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "sandboxed_api/sandbox2/util/file_helpers.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/raw_logging.h" + +namespace sandbox2 { +namespace sanitizer { +namespace { + +constexpr char kProcSelfFd[] = "/proc/self/fd"; + +// Reads filenames inside the directory and converts them to numerical values. +bool ListNumericalDirectoryEntries(const std::string& directory, + std::set* nums) { + std::vector entries; + std::string error; + if (!file_util::fileops::ListDirectoryEntries(directory, &entries, &error)) { + SAPI_RAW_LOG(WARNING, "List directory entries for '%s' failed: %s", + kProcSelfFd, error); + return false; + } + for (const auto& entry : entries) { + int num; + if (!absl::SimpleAtoi(entry, &num)) { + SAPI_RAW_LOG(WARNING, "Cannot convert %s to a number", entry); + return false; + } + nums->insert(num); + } + return true; +} + +// Returns the specified line from /proc//status. +std::string GetProcStatusLine(int pid, const std::string& value) { + const std::string fname = absl::StrCat("/proc/", pid, "/status"); + std::string procpidstatus; + auto status = file::GetContents(fname, &procpidstatus, file::Defaults()); + if (!status.ok()) { + SAPI_RAW_LOG(WARNING, "%s", status.message()); + return ""; + } + + for (const auto& line : absl::StrSplit(procpidstatus, '\n')) { + std::pair kv = + absl::StrSplit(line, absl::MaxSplits(':', 1)); + SAPI_RAW_VLOG(3, "Key: '%s' Value: '%s'", kv.first, kv.second); + if (kv.first == value) { + return std::string(kv.second); + } + } + SAPI_RAW_LOG(ERROR, "No '%s' field found in '%s'", value, fname); + return ""; +} + +} // namespace + +bool GetListOfFDs(std::set* fds) { + if (!ListNumericalDirectoryEntries(kProcSelfFd, fds)) { + return false; + } + // Exclude the dirfd which was opened in ListDirectoryEntries. + // Most probably will be the highest fd, hence the reverse order. + for (auto it = fds->rbegin(), end = fds->rend(); it != end; ++it) { + if (access(absl::StrCat(kProcSelfFd, "/", *it).c_str(), F_OK) != 0) { + fds->erase(*it); + break; + } + } + return true; +} + +bool GetListOfTasks(int pid, std::set* tasks) { + const std::string task_dir = absl::StrCat("/proc/", pid, "/task"); + return ListNumericalDirectoryEntries(task_dir, tasks); +} + +bool CloseAllFDsExcept(const std::set& fd_exceptions) { + std::set fds; + if (!GetListOfFDs(&fds)) { + return false; + } + + for (auto fd : fds) { + if (fd_exceptions.find(fd) != fd_exceptions.end()) { + continue; + } + SAPI_RAW_VLOG(2, "Closing FD:%d", fd); + close(fd); + } + return true; +} + +bool MarkAllFDsAsCOEExcept(const std::set& fd_exceptions) { + std::set fds; + if (!GetListOfFDs(&fds)) { + return false; + } + + for (auto fd : fds) { + if (fd_exceptions.find(fd) != fd_exceptions.end()) { + continue; + } + + SAPI_RAW_VLOG(2, "Marking FD:%d as close-on-exec", fd); + + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + SAPI_RAW_PLOG(ERROR, "fcntl(%d, F_GETFD) failed", fd); + return false; + } + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + SAPI_RAW_PLOG(ERROR, "fcntl(%d, F_SETFD, %x | FD_CLOEXEC) failed", fd, + flags); + return false; + } + } + + return true; +} + +int GetNumberOfThreads(int pid) { + std::string thread_str = GetProcStatusLine(pid, "Threads"); + if (thread_str.empty()) { + return -1; + } + int threads; + if (!absl::SimpleAtoi(thread_str, &threads)) { + SAPI_RAW_LOG(ERROR, "Couldn't convert '%s' to a number", thread_str); + return -1; + } + SAPI_RAW_VLOG(1, "Found %d threads in pid: %d", threads, pid); + return threads; +} + +void WaitForTsan() { +#if defined(THREAD_SANITIZER) + static bool ABSL_ATTRIBUTE_UNUSED dummy_tsan_once = []() { + __sanitizer_sandbox_on_notify(nullptr); + return true; + }(); + const pid_t pid = getpid(); + int threads; + for (int retry = 0; retry < 10; ++retry) { + threads = GetNumberOfThreads(pid); + if (threads == -1 || threads == 1) { + break; + } + absl::SleepFor(absl::Milliseconds(100)); + } +#endif +} + +bool SanitizeCurrentProcess(const std::set& fd_exceptions, + bool close_fds) { + SAPI_RAW_VLOG(1, "Sanitizing PID: %d, close_fds: %d", syscall(__NR_getpid), + close_fds); + + // Put process in a separate session (and a new process group). + setsid(); + + // 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) failed"); + return false; + } + + // Close or mark as close-on-exec open file descriptors. + if (close_fds) { + if (!CloseAllFDsExcept(fd_exceptions)) { + SAPI_RAW_LOG(ERROR, "Failed to close all fds"); + return false; + } + } else { + if (!MarkAllFDsAsCOEExcept(fd_exceptions)) { + SAPI_RAW_LOG(ERROR, "Failed to mark all fds as closed"); + return false; + } + } + return true; +} + +} // namespace sanitizer +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/sanitizer.h b/sandboxed_api/sandbox2/sanitizer.h new file mode 100644 index 0000000..34465d7 --- /dev/null +++ b/sandboxed_api/sandbox2/sanitizer.h @@ -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. + +// The sandbox2::sanitizer namespace provides functions which bring a process +// into a state in which it can be safely sandboxed. + +#ifndef SANDBOXED_API_SANDBOX2_SANITIZER_H_ +#define SANDBOXED_API_SANDBOX2_SANITIZER_H_ + +#include +#include + +#include "absl/base/macros.h" + +namespace sandbox2 { +namespace sanitizer { + +// Reads a list of open file descriptors in the current process. +bool GetListOfFDs(std::set* fds); + +// Closes all file descriptors in the current process except the ones in +// fd_exceptions. +bool CloseAllFDsExcept(const std::set& fd_exceptions); + +// Marks all file descriptors as close-on-exec, except the ones in +// fd_exceptions. +bool MarkAllFDsAsCOEExcept(const std::set& fd_exceptions); + +// Returns the number of threads in the process 'pid'. Returns -1 in case of +// errors. +int GetNumberOfThreads(int pid); + +// When running under TSAN, it will spawn a background thread. This is not +// desirable for sandboxing purposes. We will notify its background thread +// that we wish for it to finish and then wait for it to be done. It is safe +// to call this function more than once, since it keeps track of whether it +// has already notified TSAN. +// This function does nothing if not running under TSAN. +void WaitForTsan(); + +// Sanitizes current process (which will not execve a sandboxed binary). +// File-descriptors in fd_exceptions will be either closed +// (close_fds == true), or marked as close-on-exec (close_fds == false). +bool SanitizeCurrentProcess(const std::set& fd_exceptions, bool close_fds); + +// Returns a list of tasks for a pid. +bool GetListOfTasks(int pid, std::set* tasks); + +} // namespace sanitizer +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_SANITIZER_H_ diff --git a/sandboxed_api/sandbox2/sanitizer_test.cc b/sandboxed_api/sandbox2/sanitizer_test.cc new file mode 100644 index 0000000..ed99f43 --- /dev/null +++ b/sandboxed_api/sandbox2/sanitizer_test.cc @@ -0,0 +1,150 @@ +// 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/sanitizer.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/executor.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/sandbox2/util.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/util/status_matchers.h" + +using ::testing::Eq; +using ::testing::Gt; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::Ne; + +namespace sandbox2 { +namespace { + +// Runs a new process and returns 0 if the process terminated with 0. +static int RunTestcase(const std::string& path, const std::vector& args) { + pid_t pid = fork(); + if (pid < 0) { + PLOG(ERROR) << "fork()"; + return 1; + } + if (pid == 0) { + const char** argv = util::VecStringToCharPtrArr(args); + execv(path.c_str(), const_cast(argv)); + PLOG(ERROR) << "execv('" << path << "')"; + exit(EXIT_FAILURE); + } + + for (;;) { + int status; + while (wait4(pid, &status, __WALL, nullptr) != pid) { + } + + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + if (WIFSIGNALED(status)) { + LOG(ERROR) << "PID: " << pid << " signaled with: " << WTERMSIG(status); + return 128 + WTERMSIG(status); + } + } +} + +static bool IsFdOpen(int fd) { + int ret = fcntl(fd, F_GETFD); + if (ret == -1) { + VLOG(1) << "FD: " << fd << " is closed"; + return false; + } + VLOG(1) << "FD: " << fd << " is open"; + return true; +} + +// Test that marking file descriptors as close-on-exec works. +TEST(SanitizerTest, TestMarkFDsAsCOE) { + // Open a few file descriptors in non-close-on-exec mode. + int sock_fd[2]; + ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fd), Ne(-1)); + ASSERT_THAT(open("/dev/full", O_RDONLY), Ne(-1)); + int null_fd = open("/dev/null", O_RDWR); + ASSERT_THAT(null_fd, Ne(-1)); + + const std::set exceptions = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, + null_fd}; + ASSERT_THAT(sanitizer::MarkAllFDsAsCOEExcept(exceptions), IsTrue()); + + const std::string path = GetTestSourcePath("sandbox2/testcases/sanitizer"); + std::vector args; + for (auto fd : exceptions) { + args.push_back(absl::StrCat(fd)); + } + ASSERT_THAT(RunTestcase(path, args), Eq(0)); +} + +// Test that default sanitizer leaves only 0/1/2 and 1023 (client comms FD) +// open but closes the rest. +TEST(SanitizerTest, TestSandboxedBinary) { + SKIP_SANITIZERS_AND_COVERAGE; + // Open a few file descriptors in non-close-on-exec mode. + int sock_fd[2]; + ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fd), Ne(-1)); + ASSERT_THAT(open("/dev/full", O_RDONLY), Ne(-1)); + ASSERT_THAT(open("/dev/null", O_RDWR), Ne(-1)); + + const std::string path = GetTestSourcePath("sandbox2/testcases/sanitizer"); + std::vector args = { + absl::StrCat(STDIN_FILENO), + absl::StrCat(STDOUT_FILENO), + absl::StrCat(STDERR_FILENO), + absl::StrCat(Comms::kSandbox2ClientCommsFD), + }; + auto executor = absl::make_unique(path, args); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder() + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .TryBuild()); + + 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(SanitizerTest, TestGetProcStatusLine) { + // Test indirectly, GetNumberOfThreads() looks for the "Threads" value. + EXPECT_THAT(sanitizer::GetNumberOfThreads(getpid()), Gt(0)); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/stack-trace.cc b/sandboxed_api/sandbox2/stack-trace.cc new file mode 100644 index 0000000..ebb8218 --- /dev/null +++ b/sandboxed_api/sandbox2/stack-trace.cc @@ -0,0 +1,321 @@ +// 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::StackTrace class. + +#include "sandboxed_api/sandbox2/stack-trace.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "sandboxed_api/util/flag.h" +#include "absl/memory/memory.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/strip.h" +#include +#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/regs.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/sandbox2.h" +#include "sandboxed_api/sandbox2/unwind/unwind.h" +#include "sandboxed_api/sandbox2/unwind/unwind.pb.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/path.h" + +ABSL_FLAG(bool, sandbox_disable_all_stack_traces, false, + "Completely disable stack trace collection for sandboxees"); + +ABSL_FLAG(bool, sandbox_libunwind_crash_handler, true, + "Sandbox libunwind when handling violations (preferred)"); + +namespace sandbox2 { + +class StackTracePeer { + public: + static std::unique_ptr GetPolicy(pid_t target_pid, + const std::string& maps_file, + const std::string& app_path, + const std::string& exe_path, + const Mounts& mounts); + + static bool LaunchLibunwindSandbox(const Regs* regs, const Mounts& mounts, + UnwindResult* result, const std::string& delim); +}; + +std::unique_ptr StackTracePeer::GetPolicy(pid_t target_pid, + const std::string& maps_file, + const std::string& app_path, + const std::string& exe_path, + const Mounts& mounts) { + PolicyBuilder builder; + builder + // Use the mounttree of the original executable as starting point. + .SetMounts(mounts) + .AllowOpen() + .AllowRead() + .AllowWrite() + .AllowSyscall(__NR_close) + .AllowMmap() + .AllowExit() + .AllowHandleSignals() + + // libunwind + .AllowSyscall(__NR_fstat) + .AllowSyscall(__NR_lseek) + .AllowSyscall(__NR_mincore) + .AllowSyscall(__NR_mprotect) + .AllowSyscall(__NR_munmap) + .AllowSyscall(__NR_pipe2) + + // Symbolizer + .AllowSyscall(__NR_brk) + .AllowSyscall(__NR_clock_gettime) + + // Other + .AllowSyscall(__NR_dup) + .AllowSyscall(__NR_fcntl) + .AllowSyscall(__NR_getpid) + .AllowSyscall(__NR_gettid) + .AllowSyscall(__NR_madvise) + + // Required for our ptrace replacement. + .AddPolicyOnSyscall( + __NR_process_vm_readv, + { + // The pid technically is a 64bit int, however + // Linux usually uses max 16 bit, so we are fine + // with comparing only 32 bits here. + ARG_32(0), + JEQ32(static_cast(target_pid), ALLOW), + JEQ32(static_cast(1), ALLOW), + }) + + .EnableNamespaces() + + // Add proc maps. + .AddFileAt(maps_file, + file::JoinPath("/proc", absl::StrCat(target_pid), "maps")) + .AddFileAt(maps_file, + file::JoinPath("/proc", absl::StrCat(target_pid), "task", + absl::StrCat(target_pid), "maps")) + + // Add the binary itself. + .AddFileAt(exe_path, app_path); + + // Add all possible libraries without the need of parsing the binary + // or /proc/pid/maps. + for (const auto& library_path : { + "/usr/lib", + "/lib", + }) { + if (access(library_path, F_OK) != -1) { + VLOG(1) << "Adding library folder '" << library_path << "'"; + builder.AddDirectory(library_path); + } else { + VLOG(1) << "Could not add library folder '" << library_path + << "' as it does not exist"; + } + } + + auto policy_or = builder.TryBuild(); + if (!policy_or.ok()) { + LOG(ERROR) << "Creating stack unwinder sandbox policy failed"; + return nullptr; + } + std::unique_ptr policy = std::move(policy_or).ValueOrDie(); + auto keep_capabilities = absl::make_unique>(); + keep_capabilities->push_back(CAP_SYS_PTRACE); + policy->AllowUnsafeKeepCapabilities(std::move(keep_capabilities)); + // Use no special namespace flags when cloning. We will join an existing + // user namespace and will unshare() afterwards (See forkserver.cc). + policy->GetNamespace()->clone_flags_ = 0; + return policy; +} + +bool StackTracePeer::LaunchLibunwindSandbox(const Regs* regs, + const Mounts& mounts, + sandbox2::UnwindResult* result, + const std::string& delim) { + const pid_t pid = regs->pid(); + + // Tell executor to use this special internal mode. + std::vector argv; + std::vector envp; + + // We're not using absl::make_unique here as we're a friend of this specific + // constructor and using make_unique won't work. + auto executor = absl::WrapUnique(new Executor(pid)); + + executor->limits() + ->set_rlimit_as(RLIM64_INFINITY) + .set_rlimit_cpu(10) + .set_walltime_limit(absl::Seconds(5)); + + // Temporary directory used to provide files from /proc to the unwind sandbox. + char unwind_temp_directory_template[] = "/tmp/.sandbox2_unwind_XXXXXX"; + char* unwind_temp_directory = mkdtemp(unwind_temp_directory_template); + if (!unwind_temp_directory) { + LOG(WARNING) << "Could not create temporary directory for unwinding"; + return false; + } + struct UnwindTempDirectoryCleanup { + ~UnwindTempDirectoryCleanup() { + file_util::fileops::DeleteRecursively(capture); + } + char* capture; + } cleanup{unwind_temp_directory}; + + // Copy over important files from the /proc directory as we can't mount them. + const std::string unwind_temp_maps_path = + file::JoinPath(unwind_temp_directory, "maps"); + + if (!file_util::fileops::CopyFile( + file::JoinPath("/proc", absl::StrCat(pid), "maps"), + unwind_temp_maps_path, 0400)) { + LOG(WARNING) << "Could not copy maps file"; + return false; + } + + // Get path to the binary. + // app_path contains the path like it is also in /proc/pid/maps. This is + // important when the file was removed, it will have a ' (deleted)' suffix. + std::string app_path; + // The exe_path will have a mountable path of the application, even if it was + // removed. + std::string exe_path; + std::string proc_pid_exe = file::JoinPath("/proc", absl::StrCat(pid), "exe"); + if (!file_util::fileops::ReadLinkAbsolute(proc_pid_exe, &app_path)) { + LOG(WARNING) << "Could not obtain absolute path to the binary"; + return false; + } + + // Check whether the file still exists or not (SAPI). + if (access(app_path.c_str(), F_OK) == -1) { + LOG(WARNING) << "File was removed, using /proc/pid/exe."; + app_path = std::string(absl::StripSuffix(app_path, " (deleted)")); + // Create a copy of /proc/pid/exe, mount that one. + exe_path = file::JoinPath(unwind_temp_directory, "exe"); + if (!file_util::fileops::CopyFile(proc_pid_exe, exe_path, 0700)) { + LOG(WARNING) << "Could not copy /proc/pid/exe"; + return false; + } + } else { + exe_path = app_path; + } + + VLOG(1) << "Resolved binary: " << app_path << " / " << exe_path; + + // Add mappings for the binary (as they might not have been added due to the + // forkserver). + auto policy = StackTracePeer::GetPolicy(pid, unwind_temp_maps_path, app_path, + exe_path, mounts); + if (!policy) { + return false; + } + auto comms = executor->ipc()->comms(); + Sandbox2 s2(std::move(executor), std::move(policy)); + + VLOG(1) << "Running libunwind sandbox"; + s2.RunAsync(); + UnwindSetup msg; + msg.set_pid(pid); + msg.set_regs(reinterpret_cast(®s->user_regs_), + sizeof(regs->user_regs_)); + msg.set_default_max_frames(kDefaultMaxFrames); + msg.set_delim(delim.c_str(), delim.size()); + + bool success = true; + if (!comms->SendProtoBuf(msg)) { + LOG(ERROR) << "Sending libunwind setup message failed"; + success = false; + } + + if (success && !comms->RecvProtoBuf(result)) { + LOG(ERROR) << "Receiving libunwind result failed"; + success = false; + } + + if (!success) { + s2.Kill(); + } + auto s2_result = s2.AwaitResult(); + + LOG(INFO) << "Libunwind execution status: " << s2_result.ToString(); + + return success && s2_result.final_status() == Result::OK; +} + +std::string GetStackTrace(const Regs* regs, const Mounts& mounts, + const std::string& delim) { + if (absl::GetFlag(FLAGS_sandbox_disable_all_stack_traces)) { + return ""; + } + if (!regs) { + LOG(WARNING) << "Could not obtain stacktrace, regs == nullptr"; + return "[ERROR (noregs)]"; + } + +#if defined(THREAD_SANITIZER) || defined(ADDRESS_SANITIZER) || \ + defined(MEMORY_SANITIZER) + constexpr bool kSanitizerEnabled = true; +#else + constexpr bool kSanitizerEnabled = false; +#endif + + const bool coverage_enabled = + getenv("COVERAGE"); + + // Show a warning if sandboxed libunwind is requested but we're running in + // an ASAN / coverage build (= we can't use sandboxed libunwind). + if (absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler) && + (kSanitizerEnabled || coverage_enabled)) { + LOG_IF(WARNING, kSanitizerEnabled) + << "Sanitizer build, using non-sandboxed libunwind"; + LOG_IF(WARNING, coverage_enabled) + << "Coverage build, using non-sandboxed libunwind"; + return UnsafeGetStackTrace(regs->pid(), delim); + } + + if (!absl::GetFlag(FLAGS_sandbox_libunwind_crash_handler)) { + return UnsafeGetStackTrace(regs->pid(), delim); + } + UnwindResult res; + + if (!StackTracePeer::LaunchLibunwindSandbox(regs, mounts, &res, delim)) { + return ""; + } + return res.stacktrace(); +} + +std::string UnsafeGetStackTrace(pid_t pid, const std::string& delim) { + LOG(WARNING) << "Using non-sandboxed libunwind"; + std::string stack_trace; + std::vector ips; + RunLibUnwindAndSymbolizer(pid, &stack_trace, &ips, kDefaultMaxFrames, delim); + return stack_trace; +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/stack-trace.h b/sandboxed_api/sandbox2/stack-trace.h new file mode 100644 index 0000000..f885291 --- /dev/null +++ b/sandboxed_api/sandbox2/stack-trace.h @@ -0,0 +1,51 @@ +// 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::StackTrace class provides static methods useful when analyzing +// call-stack of the process. It uses libunwind-ptrace, so the process must be +// in a stopped state to call those methods. + +#ifndef SANDBOXED_API_SANDBOX2_STACK_TRACE_H__ +#define SANDBOXED_API_SANDBOX2_STACK_TRACE_H__ + +#include +#include +#include +#include + +#include "sandboxed_api/util/flag.h" +#include "sandboxed_api/sandbox2/mounts.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/regs.h" +#include "sandboxed_api/sandbox2/unwind/unwind.pb.h" + +// Exposed for testing only +ABSL_DECLARE_FLAG(bool, sandbox_libunwind_crash_handler); + +namespace sandbox2 { + +// Maximum depth of analyzed call stack. +constexpr size_t kDefaultMaxFrames = 200; + +// Returns the stack-trace of the PID=pid, delimited by the delim argument. +std::string GetStackTrace(const Regs* regs, const Mounts& mounts, + const std::string& delim = " "); + +// Similar to GetStackTrace() but without using the sandbox to isolate +// libunwind. +std::string UnsafeGetStackTrace(pid_t pid, const std::string& delim = " "); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_STACK_TRACE_H__ diff --git a/sandboxed_api/sandbox2/stack-trace_test.cc b/sandboxed_api/sandbox2/stack-trace_test.cc new file mode 100644 index 0000000..1fc0e58 --- /dev/null +++ b/sandboxed_api/sandbox2/stack-trace_test.cc @@ -0,0 +1,190 @@ +// 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 + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/util/flag.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/global_forkclient.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/stack-trace.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/temp_file.h" +#include "sandboxed_api/util/status_matchers.h" + +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::Not; + +namespace sandbox2 { +namespace { + +// Temporarily overrides a flag, restores the original flag value when it goes +// out of scope. +template +class TemporaryFlagOverride { + public: + using Flag = T; + TemporaryFlagOverride(Flag* flag, T value) + : flag_(flag), original_value_(absl::GetFlag(*flag)) { + absl::SetFlag(flag, value); + } + + ~TemporaryFlagOverride() { absl::SetFlag(flag_, original_value_); } + + private: + Flag* flag_; + T original_value_; +}; + +// Test that symbolization of stack traces works. +void SymbolizationWorksCommon( + const std::function& modify_policy) { + const std::string path = GetTestSourcePath("sandbox2/testcases/symbolize"); + std::vector args = {path, "1"}; + auto executor = absl::make_unique(path, args); + + std::string temp_filename = CreateNamedTempFileAndClose("/tmp/").ValueOrDie(); + file_util::fileops::CopyFile("/proc/cpuinfo", temp_filename, 0444); + struct TempCleanup { + ~TempCleanup() { remove(capture->c_str()); } + std::string* capture; + } temp_cleanup{&temp_filename}; + + PolicyBuilder policybuilder; + policybuilder + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .EnableNamespaces() + .AddFile(path) + .AddLibrariesForBinary(path) + .AddFileAt(temp_filename, "/proc/cpuinfo"); + + modify_policy(&policybuilder); + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, policybuilder.TryBuild()); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::SIGNALED)); + ASSERT_THAT(result.GetStackTrace(), HasSubstr("CrashMe")); +} + +TEST(StackTraceTest, SymbolizationWorksNonSandboxedLibunwind) { + SKIP_SANITIZERS_AND_COVERAGE; + TemporaryFlagOverride temp_override( + &FLAGS_sandbox_libunwind_crash_handler, false); + SymbolizationWorksCommon([](PolicyBuilder*) {}); +} + +TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwind) { + SKIP_SANITIZERS_AND_COVERAGE; + TemporaryFlagOverride temp_override( + &FLAGS_sandbox_libunwind_crash_handler, true); + SymbolizationWorksCommon([](PolicyBuilder*) {}); +} + +TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwindProcDirMounted) { + SKIP_SANITIZERS_AND_COVERAGE; + TemporaryFlagOverride temp_override( + &FLAGS_sandbox_libunwind_crash_handler, true); + SymbolizationWorksCommon( + [](PolicyBuilder* builder) { builder->AddDirectory("/proc"); }); +} + +TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwindProcFileMounted) { + SKIP_SANITIZERS_AND_COVERAGE; + TemporaryFlagOverride temp_override( + &FLAGS_sandbox_libunwind_crash_handler, true); + SymbolizationWorksCommon([](PolicyBuilder* builder) { + builder->AddFile("/proc/sys/vm/overcommit_memory"); + }); +} + +TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwindSysDirMounted) { + SKIP_SANITIZERS_AND_COVERAGE; + TemporaryFlagOverride temp_override( + &FLAGS_sandbox_libunwind_crash_handler, true); + SymbolizationWorksCommon( + [](PolicyBuilder* builder) { builder->AddDirectory("/sys"); }); +} + +TEST(StackTraceTest, SymbolizationWorksSandboxedLibunwindSysFileMounted) { + SKIP_SANITIZERS_AND_COVERAGE; + TemporaryFlagOverride temp_override( + &FLAGS_sandbox_libunwind_crash_handler, true); + SymbolizationWorksCommon([](PolicyBuilder* builder) { + builder->AddFile("/sys/devices/system/cpu/online"); + }); +} + +static size_t FileCountInDirectory(const std::string& path) { + std::vector fds; + std::string error; + CHECK(file_util::fileops::ListDirectoryEntries(path, &fds, &error)); + return fds.size(); +} + +TEST(StackTraceTest, ForkEnterNsLibunwindDoesNotLeakFDs) { + SKIP_SANITIZERS_AND_COVERAGE; + // Get list of open FDs in the global forkserver. + pid_t forkserver_pid = GetGlobalForkServerPid(); + std::string forkserver_fd_path = absl::StrCat("/proc/", forkserver_pid, "/fd"); + size_t filecount_before = FileCountInDirectory(forkserver_fd_path); + + TemporaryFlagOverride temp_override( + &FLAGS_sandbox_libunwind_crash_handler, true); + SymbolizationWorksCommon([](PolicyBuilder* builder) { + builder->AddFile("/sys/devices/system/cpu/online"); + }); + + EXPECT_THAT(filecount_before, Eq(FileCountInDirectory(forkserver_fd_path))); +} + +// Test that symbolization skips writeable files (attack vector). +TEST(StackTraceTest, SymbolizationTrustedFilesOnly) { + SKIP_SANITIZERS_AND_COVERAGE; + const std::string path = GetTestSourcePath("sandbox2/testcases/symbolize"); + std::vector args = {path, "2"}; + auto executor = absl::make_unique(path, args); + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, PolicyBuilder{} + // Don't restrict the syscalls at all. + .DangerDefaultAllowAll() + .EnableNamespaces() + .AddFile(path) + .AddLibrariesForBinary(path) + .TryBuild()); + + Sandbox2 s2(std::move(executor), std::move(policy)); + auto result = s2.Run(); + + ASSERT_THAT(result.final_status(), Eq(Result::SIGNALED)); + ASSERT_THAT(result.GetStackTrace(), Not(HasSubstr("CrashMe"))); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/syscall.cc b/sandboxed_api/sandbox2/syscall.cc new file mode 100644 index 0000000..316fe7a --- /dev/null +++ b/sandboxed_api/sandbox2/syscall.cc @@ -0,0 +1,112 @@ +// 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::Syscall class. + +#include "sandboxed_api/sandbox2/syscall.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "sandboxed_api/sandbox2/syscall_defs.h" + +#ifndef AUDIT_ARCH_PPC64LE +#define AUDIT_ARCH_PPC64LE (EM_PPC64 | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) +#endif + +namespace sandbox2 { + +std::string Syscall::GetArchDescription(CpuArch arch) { + switch (arch) { + case kX86_64: + return "[X86-64]"; + case kX86_32: + return "[X86-32]"; + case kPPC_64: + return "[PPC-64]"; + default: + LOG(ERROR) << "Unknown CPU architecture: " << arch; + return absl::StrFormat("[UNKNOWN_ARCH:%d]", arch); + } +} + +Syscall::CpuArch Syscall::GetHostArch() { +#if defined(__x86_64__) + return kX86_64; +#elif defined(__i386__) + return kX86_32; +#elif defined(__powerpc64__) + return kPPC_64; +#endif +} + +uint32_t Syscall::GetHostAuditArch() { +#if defined(__x86_64__) + return AUDIT_ARCH_X86_64; +#elif defined(__i386__) + return AUDIT_ARCH_I386; +#elif defined(__powerpc64__) + return AUDIT_ARCH_PPC64LE; +#endif +} + +namespace { + +// Syscall entry in syscall table for the architecture +const SyscallTable GetSyscallTable(Syscall::CpuArch arch) { + switch (arch) { +#if defined(__x86_64__) + case Syscall::kX86_64: + return SyscallTable::kSyscallDataX8664; + case Syscall::kX86_32: + return SyscallTable::kSyscallDataX8632; +#elif defined(__powerpc64__) + case Syscall::kPPC_64: + return SyscallTable::kSyscallDataPPC64; +#endif + default: + return SyscallTable(); + } +} + +} // namespace + +std::string Syscall::GetName() const { + const char* name = GetSyscallTable(arch_).GetEntry(nr_).name; + if (name == nullptr) { + return absl::StrFormat("UNKNOWN[%d/0x%x]", nr_, nr_); + } + return name; +} + +std::vector Syscall::GetArgumentsDescription() const { + return GetSyscallTable(arch_).GetEntry(nr_).GetArgumentsDescription( + args_.data(), pid_); +} + +std::string Syscall::GetDescription() const { + const auto& arch = GetArchDescription(arch_); + const std::string args = absl::StrJoin(GetArgumentsDescription(), ", "); + return absl::StrFormat("%s %s [%d](%s) IP: %#x, STACK: %#x", arch, GetName(), + nr_, args, ip_, sp_); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/syscall.h b/sandboxed_api/sandbox2/syscall.h new file mode 100644 index 0000000..f6210dc --- /dev/null +++ b/sandboxed_api/sandbox2/syscall.h @@ -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. + +// The sandbox2::Syscalls class defines mostly static helper methods which +// are used to analyze status of the ptraced process + +#ifndef SANDBOXED_API_SANDBOX2_SYSCALL_H__ +#define SANDBOXED_API_SANDBOX2_SYSCALL_H__ + +#include + +#include +#include +#include +#include +#include + +namespace sandbox2 { + +class Syscall { + public: + // Supported CPU architectures. + // Linux: Use a magic value, so it can be easily spotted in the seccomp-bpf + // bytecode decompilation stream. Must be < (1<<15), as/ that's the size of + // data which can be returned by BPF. + enum CpuArch { + kUnknown = 0xCAF0, + kX86_64, + kX86_32, + kPPC_64, + }; + // Maximum number of syscall arguments + static constexpr size_t kMaxArgs = 6; + using Args = std::array; + + // Returns the host architecture, according to CpuArch. + static CpuArch GetHostArch(); + + // Returns the host architecture, according to . + static uint32_t GetHostAuditArch(); + + // Returns a description of the architecture. + static std::string GetArchDescription(CpuArch arch); + + Syscall() = default; + Syscall(CpuArch arch, uint64_t nr, Args args = {}) + : arch_(arch), nr_(nr), args_(args) {} + + pid_t pid() const { return pid_; } + uint64_t nr() const { return nr_; } + CpuArch arch() const { return arch_; } + const Args& args() const { return args_; } + uint64_t stack_pointer() const { return sp_; } + uint64_t instruction_pointer() const { return ip_; } + + std::string GetName() const; + + std::vector GetArgumentsDescription() const; + std::string GetDescription() const; + + private: + friend class Regs; + + Syscall(pid_t pid) : pid_(pid) {} + Syscall(CpuArch arch, uint64_t nr, Args args, pid_t pid, uint64_t sp, + uint64_t ip) + : arch_(arch), nr_(nr), args_(args), pid_(pid), sp_(sp), ip_(ip) {} + + CpuArch arch_ = kUnknown; + uint64_t nr_ = -1; + Args args_ = {}; + pid_t pid_ = -1; + uint64_t sp_ = 0; + uint64_t ip_ = 0; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_SYSCALL_H__ diff --git a/sandboxed_api/sandbox2/syscall_defs.cc b/sandboxed_api/sandbox2/syscall_defs.cc new file mode 100644 index 0000000..2bb35f0 --- /dev/null +++ b/sandboxed_api/sandbox2/syscall_defs.cc @@ -0,0 +1,1191 @@ +#include "sandboxed_api/sandbox2/syscall_defs.h" + +#include +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "sandboxed_api/sandbox2/util.h" + +namespace sandbox2 { + +namespace { + +std::string GetArgumentDescription(uint64_t value, SyscallTable::ArgType type, + pid_t pid) { + std::string ret = absl::StrFormat("%#x", value); + switch (type) { + case SyscallTable::kOct: + absl::StrAppendFormat(&ret, " [\\0%o]", value); + break; + case SyscallTable::kPath: { + auto path_or = util::ReadCPathFromPid(pid, value); + if (path_or.ok()) { + std::string path = path_or.ValueOrDie(); + absl::StrAppendFormat(&ret, " ['%s']", absl::CHexEscape(path)); + } else { + absl::StrAppend(&ret, " [unreadable path]"); + } + } break; + case SyscallTable::kInt: + absl::StrAppendFormat(&ret, " [%d]", value); + break; + default: + break; + } + return ret; +} + +} // namespace + +std::vector SyscallTable::Entry::GetArgumentsDescription( + const uint64_t values[], pid_t pid) const { + int num_args = GetNumArgs(); + std::vector rv; + rv.reserve(num_args); + for (int i = 0; i < num_args; ++i) { + rv.emplace_back(GetArgumentDescription(values[i], arg_types[i], pid)); + } + return rv; +} + +// TODO(wiktorg): Rewrite it without macros - might be easier after C++17 switch +#define SYSCALL_HELPER(n, name, arg1, arg2, arg3, arg4, arg5, arg6, ...) \ + { \ + name, n, { \ + SyscallTable::arg1, SyscallTable::arg2, SyscallTable::arg3, \ + SyscallTable::arg4, SyscallTable::arg5, SyscallTable::arg6 \ + } \ + } +#define SYSCALL_NUM_ARGS_HELPER(_0, _1, _2, _3, _4, _5, _6, N, ...) N +#define SYSCALL_NUM_ARGS(...) \ + SYSCALL_NUM_ARGS_HELPER(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0) +#define SYSCALL(...) \ + SYSCALL_HELPER(SYSCALL_NUM_ARGS(__VA_ARGS__), __VA_ARGS__, kGen, kGen, kGen, \ + kGen, kGen, kGen) +#define SYSCALL_WITH_UNKNOWN_ARGS(name) \ + SYSCALL_HELPER(-1, name, kGen, kGen, kGen, kGen, kGen, kGen) + +#define SYSCALLS_UNUSED(name) SYSCALL(name, kHex, kHex, kHex, kHex, kHex, kHex) + +#define SYSCALLS_UNUSED0_9(prefix) \ + SYSCALLS_UNUSED(prefix "0"), SYSCALLS_UNUSED(prefix "1"), \ + SYSCALLS_UNUSED(prefix "2"), SYSCALLS_UNUSED(prefix "3"), \ + SYSCALLS_UNUSED(prefix "4"), SYSCALLS_UNUSED(prefix "5"), \ + SYSCALLS_UNUSED(prefix "6"), SYSCALLS_UNUSED(prefix "7"), \ + SYSCALLS_UNUSED(prefix "8"), SYSCALLS_UNUSED(prefix "9") + +#define SYSCALLS_UNUSED00_49(prefix) \ + SYSCALLS_UNUSED0_9(prefix "0"), SYSCALLS_UNUSED0_9(prefix "1"), \ + SYSCALLS_UNUSED0_9(prefix "2"), SYSCALLS_UNUSED0_9(prefix "3"), \ + SYSCALLS_UNUSED0_9(prefix "4") +#define SYSCALLS_UNUSED50_99(prefix) \ + SYSCALLS_UNUSED0_9(prefix "5"), SYSCALLS_UNUSED0_9(prefix "6"), \ + SYSCALLS_UNUSED0_9(prefix "7"), SYSCALLS_UNUSED0_9(prefix "8"), \ + SYSCALLS_UNUSED0_9(prefix "9") +#define SYSCALLS_UNUSED00_99(prefix) \ + SYSCALLS_UNUSED00_49(prefix), SYSCALLS_UNUSED50_99(prefix) + +#if defined(__x86_64__) +// Syscall description table for Linux x86_64 +const absl::Span SyscallTable::kSyscallDataX8664 = { + SYSCALL("read", kInt, kHex, kInt), // 0 + SYSCALL("write", kInt, kHex, kInt), // 1 + SYSCALL("open", kPath, kHex, kOct), // 2 + SYSCALL("close", kInt), // 3 + SYSCALL("stat", kPath, kGen), // 4 + SYSCALL("fstat", kInt, kHex), // 5 + SYSCALL("lstat", kPath, kGen), // 6 + SYSCALL_WITH_UNKNOWN_ARGS("poll"), // 7 + SYSCALL_WITH_UNKNOWN_ARGS("lseek"), // 8 + SYSCALL("mmap", kHex, kInt, kHex, kHex, kInt, kInt), // 9 + SYSCALL("mprotect", kHex, kHex, kHex), // 10 + SYSCALL("munmap", kHex, kHex), // 11 + SYSCALL("brk", kHex), // 12 + SYSCALL("rt_sigaction", kSignal, kHex, kHex, kInt), // 13 + SYSCALL_WITH_UNKNOWN_ARGS("rt_sigprocmask"), // 14 + SYSCALL("rt_sigreturn"), // 15 + SYSCALL_WITH_UNKNOWN_ARGS("ioctl"), // 16 + SYSCALL_WITH_UNKNOWN_ARGS("pread64"), // 17 + SYSCALL_WITH_UNKNOWN_ARGS("pwrite64"), // 18 + SYSCALL_WITH_UNKNOWN_ARGS("readv"), // 19 + SYSCALL_WITH_UNKNOWN_ARGS("writev"), // 20 + SYSCALL("access", kPath, kHex), // 21 + SYSCALL_WITH_UNKNOWN_ARGS("pipe"), // 22 + SYSCALL_WITH_UNKNOWN_ARGS("select"), // 23 + SYSCALL_WITH_UNKNOWN_ARGS("sched_yield"), // 24 + SYSCALL_WITH_UNKNOWN_ARGS("mremap"), // 25 + SYSCALL_WITH_UNKNOWN_ARGS("msync"), // 26 + SYSCALL_WITH_UNKNOWN_ARGS("mincore"), // 27 + SYSCALL_WITH_UNKNOWN_ARGS("madvise"), // 28 + SYSCALL_WITH_UNKNOWN_ARGS("shmget"), // 29 + SYSCALL_WITH_UNKNOWN_ARGS("shmat"), // 30 + SYSCALL_WITH_UNKNOWN_ARGS("shmctl"), // 31 + SYSCALL_WITH_UNKNOWN_ARGS("dup"), // 32 + SYSCALL("dup2", kGen, kGen), // 33 + SYSCALL("pause"), // 34 + SYSCALL("nanosleep", kHex, kHex), // 35 + SYSCALL_WITH_UNKNOWN_ARGS("getitimer"), // 36 + SYSCALL("alarm", kInt), // 37 + SYSCALL_WITH_UNKNOWN_ARGS("setitimer"), // 38 + SYSCALL("getpid"), // 39 + SYSCALL_WITH_UNKNOWN_ARGS("sendfile"), // 40 + SYSCALL("socket", kAddressFamily, kInt, kInt), // 41 + SYSCALL("connect", kInt, kSockaddr, kInt), // 42 + SYSCALL_WITH_UNKNOWN_ARGS("accept"), // 43 + SYSCALL("sendto", kInt, kGen, kInt, kHex, kSockaddr, kInt), // 44 + SYSCALL_WITH_UNKNOWN_ARGS("recvfrom"), // 45 + SYSCALL("sendmsg", kInt, kSockmsghdr, kHex), // 46 + SYSCALL_WITH_UNKNOWN_ARGS("recvmsg"), // 47 + SYSCALL_WITH_UNKNOWN_ARGS("shutdown"), // 48 + SYSCALL_WITH_UNKNOWN_ARGS("bind"), // 49 + SYSCALL_WITH_UNKNOWN_ARGS("listen"), // 50 + SYSCALL_WITH_UNKNOWN_ARGS("getsockname"), // 51 + SYSCALL_WITH_UNKNOWN_ARGS("getpeername"), // 52 + SYSCALL_WITH_UNKNOWN_ARGS("socketpair"), // 53 + SYSCALL_WITH_UNKNOWN_ARGS("setsockopt"), // 54 + SYSCALL_WITH_UNKNOWN_ARGS("getsockopt"), // 55 + SYSCALL("clone", kCloneFlag, kHex, kHex, kHex, kHex), // 56 + SYSCALL("fork"), // 57 + SYSCALL("vfork"), // 58 + SYSCALL("execve", kPath, kHex, kHex), // 59 + SYSCALL("exit", kInt), // 60 + SYSCALL("wait4", kInt, kHex, kHex, kHex), // 61 + SYSCALL("kill", kInt, kSignal), // 62 + SYSCALL_WITH_UNKNOWN_ARGS("uname"), // 63 + SYSCALL_WITH_UNKNOWN_ARGS("semget"), // 64 + SYSCALL_WITH_UNKNOWN_ARGS("semop"), // 65 + SYSCALL_WITH_UNKNOWN_ARGS("semctl"), // 66 + SYSCALL_WITH_UNKNOWN_ARGS("shmdt"), // 67 + SYSCALL_WITH_UNKNOWN_ARGS("msgget"), // 68 + SYSCALL_WITH_UNKNOWN_ARGS("msgsnd"), // 69 + SYSCALL_WITH_UNKNOWN_ARGS("msgrcv"), // 70 + SYSCALL_WITH_UNKNOWN_ARGS("msgctl"), // 71 + SYSCALL_WITH_UNKNOWN_ARGS("fcntl"), // 72 + SYSCALL_WITH_UNKNOWN_ARGS("flock"), // 73 + SYSCALL_WITH_UNKNOWN_ARGS("fsync"), // 74 + SYSCALL_WITH_UNKNOWN_ARGS("fdatasync"), // 75 + SYSCALL("truncate", kPath, kInt), // 76 + SYSCALL_WITH_UNKNOWN_ARGS("ftruncate"), // 77 + SYSCALL_WITH_UNKNOWN_ARGS("getdents"), // 78 + SYSCALL_WITH_UNKNOWN_ARGS("getcwd"), // 79 + SYSCALL("chdir", kPath), // 80 + SYSCALL_WITH_UNKNOWN_ARGS("fchdir"), // 81 + SYSCALL("rename", kPath, kPath), // 82 + SYSCALL("mkdir", kPath, kOct), // 83 + SYSCALL("rmdir", kPath), // 84 + SYSCALL("creat", kPath, kOct), // 85 + SYSCALL("link", kPath, kPath), // 86 + SYSCALL("unlink", kPath), // 87 + SYSCALL("symlink", kPath, kPath), // 88 + SYSCALL("readlink", kPath, kGen, kInt), // 89 + SYSCALL("chmod", kPath, kOct), // 90 + SYSCALL_WITH_UNKNOWN_ARGS("fchmod"), // 91 + SYSCALL("chown", kPath, kInt, kInt), // 92 + SYSCALL_WITH_UNKNOWN_ARGS("fchown"), // 93 + SYSCALL("lchown", kPath, kInt, kInt), // 94 + SYSCALL("umask", kHex), // 95 + SYSCALL("gettimeofday", kHex, kHex), // 96 + SYSCALL_WITH_UNKNOWN_ARGS("getrlimit"), // 97 + SYSCALL_WITH_UNKNOWN_ARGS("getrusage"), // 98 + SYSCALL_WITH_UNKNOWN_ARGS("sysinfo"), // 99 + SYSCALL_WITH_UNKNOWN_ARGS("times"), // 100 + SYSCALL("ptrace", kGen, kGen, kGen), // 101 + SYSCALL_WITH_UNKNOWN_ARGS("getuid"), // 102 + SYSCALL_WITH_UNKNOWN_ARGS("syslog"), // 103 + SYSCALL_WITH_UNKNOWN_ARGS("getgid"), // 104 + SYSCALL_WITH_UNKNOWN_ARGS("setuid"), // 105 + SYSCALL_WITH_UNKNOWN_ARGS("setgid"), // 106 + SYSCALL_WITH_UNKNOWN_ARGS("geteuid"), // 107 + SYSCALL_WITH_UNKNOWN_ARGS("getegid"), // 108 + SYSCALL_WITH_UNKNOWN_ARGS("setpgid"), // 109 + SYSCALL_WITH_UNKNOWN_ARGS("getppid"), // 110 + SYSCALL_WITH_UNKNOWN_ARGS("getpgrp"), // 111 + SYSCALL_WITH_UNKNOWN_ARGS("setsid"), // 112 + SYSCALL_WITH_UNKNOWN_ARGS("setreuid"), // 113 + SYSCALL_WITH_UNKNOWN_ARGS("setregid"), // 114 + SYSCALL_WITH_UNKNOWN_ARGS("getgroups"), // 115 + SYSCALL_WITH_UNKNOWN_ARGS("setgroups"), // 116 + SYSCALL_WITH_UNKNOWN_ARGS("setresuid"), // 117 + SYSCALL_WITH_UNKNOWN_ARGS("getresuid"), // 118 + SYSCALL_WITH_UNKNOWN_ARGS("setresgid"), // 119 + SYSCALL_WITH_UNKNOWN_ARGS("getresgid"), // 120 + SYSCALL_WITH_UNKNOWN_ARGS("getpgid"), // 121 + SYSCALL_WITH_UNKNOWN_ARGS("setfsuid"), // 122 + SYSCALL_WITH_UNKNOWN_ARGS("setfsgid"), // 123 + SYSCALL_WITH_UNKNOWN_ARGS("getsid"), // 124 + SYSCALL_WITH_UNKNOWN_ARGS("capget"), // 125 + SYSCALL_WITH_UNKNOWN_ARGS("capset"), // 126 + SYSCALL_WITH_UNKNOWN_ARGS("rt_sigpending"), // 127 + SYSCALL_WITH_UNKNOWN_ARGS("rt_sigtimedwait"), // 128 + SYSCALL_WITH_UNKNOWN_ARGS("rt_sigqueueinfo"), // 129 + SYSCALL_WITH_UNKNOWN_ARGS("rt_sigsuspend"), // 130 + SYSCALL_WITH_UNKNOWN_ARGS("sigaltstack"), // 131 + SYSCALL_WITH_UNKNOWN_ARGS("utime"), // 132 + SYSCALL("mknod", kPath, kOct, kHex), // 133 + SYSCALL("uselib", kPath), // 134 + SYSCALL_WITH_UNKNOWN_ARGS("personality"), // 135 + SYSCALL_WITH_UNKNOWN_ARGS("ustat"), // 136 + SYSCALL_WITH_UNKNOWN_ARGS("statfs"), // 137 + SYSCALL_WITH_UNKNOWN_ARGS("fstatfs"), // 138 + SYSCALL_WITH_UNKNOWN_ARGS("sysfs"), // 139 + SYSCALL_WITH_UNKNOWN_ARGS("getpriority"), // 140 + SYSCALL_WITH_UNKNOWN_ARGS("setpriority"), // 141 + SYSCALL_WITH_UNKNOWN_ARGS("sched_setparam"), // 142 + SYSCALL_WITH_UNKNOWN_ARGS("sched_getparam"), // 143 + SYSCALL_WITH_UNKNOWN_ARGS("sched_setscheduler"), // 144 + SYSCALL_WITH_UNKNOWN_ARGS("sched_getscheduler"), // 145 + SYSCALL_WITH_UNKNOWN_ARGS("sched_get_priority_max"), // 146 + SYSCALL_WITH_UNKNOWN_ARGS("sched_get_priority_min"), // 147 + SYSCALL_WITH_UNKNOWN_ARGS("sched_rr_get_interval"), // 148 + SYSCALL_WITH_UNKNOWN_ARGS("mlock"), // 149 + SYSCALL_WITH_UNKNOWN_ARGS("munlock"), // 150 + SYSCALL_WITH_UNKNOWN_ARGS("mlockall"), // 151 + SYSCALL_WITH_UNKNOWN_ARGS("munlockall"), // 152 + SYSCALL_WITH_UNKNOWN_ARGS("vhangup"), // 153 + SYSCALL_WITH_UNKNOWN_ARGS("modify_ldt"), // 154 + SYSCALL("pivot_root", kPath, kPath), // 155 + SYSCALL_WITH_UNKNOWN_ARGS("_sysctl"), // 156 + SYSCALL("prctl", kInt, kHex, kHex, kHex, kHex), // 157 + SYSCALL("arch_prctl", kInt, kHex), // 158 + SYSCALL_WITH_UNKNOWN_ARGS("adjtimex"), // 159 + SYSCALL_WITH_UNKNOWN_ARGS("setrlimit"), // 160 + SYSCALL("chroot", kPath), // 161 + SYSCALL_WITH_UNKNOWN_ARGS("sync"), // 162 + SYSCALL("acct", kPath), // 163 + SYSCALL("settimeofday", kHex, kHex), // 164 + SYSCALL("mount", kPath, kPath, kString, kHex, kGen), // 165 + SYSCALL("umount2", kPath, kHex), // 166 + SYSCALL("swapon", kPath, kHex), // 167 + SYSCALL("swapoff", kPath), // 168 + SYSCALL_WITH_UNKNOWN_ARGS("reboot"), // 169 + SYSCALL_WITH_UNKNOWN_ARGS("sethostname"), // 170 + SYSCALL_WITH_UNKNOWN_ARGS("setdomainname"), // 171 + SYSCALL_WITH_UNKNOWN_ARGS("iopl"), // 172 + SYSCALL_WITH_UNKNOWN_ARGS("ioperm"), // 173 + SYSCALL_WITH_UNKNOWN_ARGS("create_module"), // 174 + SYSCALL_WITH_UNKNOWN_ARGS("init_module"), // 175 + SYSCALL_WITH_UNKNOWN_ARGS("delete_module"), // 176 + SYSCALL_WITH_UNKNOWN_ARGS("get_kernel_syms"), // 177 + SYSCALL_WITH_UNKNOWN_ARGS("query_module"), // 178 + SYSCALL("quotactl", kInt, kPath, kInt), // 179 + SYSCALL_WITH_UNKNOWN_ARGS("nfsservctl"), // 180 + SYSCALL_WITH_UNKNOWN_ARGS("getpmsg"), // 181 + SYSCALL_WITH_UNKNOWN_ARGS("putpmsg"), // 182 + SYSCALL_WITH_UNKNOWN_ARGS("afs_syscall"), // 183 + SYSCALL_WITH_UNKNOWN_ARGS("tuxcall"), // 184 + SYSCALL_WITH_UNKNOWN_ARGS("security"), // 185 + SYSCALL("gettid"), // 186 + SYSCALL_WITH_UNKNOWN_ARGS("readahead"), // 187 + SYSCALL("setxattr", kPath, kString, kGen, kInt, kHex), // 188 + SYSCALL("lsetxattr", kPath, kString, kGen, kInt, kHex), // 189 + SYSCALL_WITH_UNKNOWN_ARGS("fsetxattr"), // 190 + SYSCALL("getxattr", kPath, kString, kGen, kInt), // 191 + SYSCALL("lgetxattr", kPath, kString, kGen, kInt), // 192 + SYSCALL_WITH_UNKNOWN_ARGS("fgetxattr"), // 193 + SYSCALL("listxattr", kPath, kGen, kInt), // 194 + SYSCALL("llistxattr", kPath, kGen, kInt), // 195 + SYSCALL_WITH_UNKNOWN_ARGS("flistxattr"), // 196 + SYSCALL("removexattr", kPath, kString), // 197 + SYSCALL_WITH_UNKNOWN_ARGS("lremovexattr"), // 198 + SYSCALL_WITH_UNKNOWN_ARGS("fremovexattr"), // 199 + SYSCALL("tkill", kInt, kSignal), // 200 + SYSCALL("time", kHex), // 201 + SYSCALL_WITH_UNKNOWN_ARGS("futex"), // 202 + SYSCALL_WITH_UNKNOWN_ARGS("sched_setaffinity"), // 203 + SYSCALL_WITH_UNKNOWN_ARGS("sched_getaffinity"), // 204 + SYSCALL("set_thread_area", kHex), // 205 + SYSCALL_WITH_UNKNOWN_ARGS("io_setup"), // 206 + SYSCALL_WITH_UNKNOWN_ARGS("io_destroy"), // 207 + SYSCALL_WITH_UNKNOWN_ARGS("io_getevents"), // 208 + SYSCALL_WITH_UNKNOWN_ARGS("io_submit"), // 209 + SYSCALL_WITH_UNKNOWN_ARGS("io_cancel"), // 210 + SYSCALL("get_thread_area", kHex), // 211 + SYSCALL_WITH_UNKNOWN_ARGS("lookup_dcookie"), // 212 + SYSCALL_WITH_UNKNOWN_ARGS("epoll_create"), // 213 + SYSCALL_WITH_UNKNOWN_ARGS("epoll_ctl_old"), // 214 + SYSCALL_WITH_UNKNOWN_ARGS("epoll_wait_old"), // 215 + SYSCALL_WITH_UNKNOWN_ARGS("remap_file_pages"), // 216 + SYSCALL_WITH_UNKNOWN_ARGS("getdents64"), // 217 + SYSCALL("set_tid_address", kHex), // 218 + SYSCALL_WITH_UNKNOWN_ARGS("restart_syscall"), // 219 + SYSCALL_WITH_UNKNOWN_ARGS("semtimedop"), // 220 + SYSCALL_WITH_UNKNOWN_ARGS("fadvise64"), // 221 + SYSCALL_WITH_UNKNOWN_ARGS("timer_create"), // 222 + SYSCALL_WITH_UNKNOWN_ARGS("timer_settime"), // 223 + SYSCALL_WITH_UNKNOWN_ARGS("timer_gettime"), // 224 + SYSCALL_WITH_UNKNOWN_ARGS("timer_getoverrun"), // 225 + SYSCALL_WITH_UNKNOWN_ARGS("timer_delete"), // 226 + SYSCALL_WITH_UNKNOWN_ARGS("clock_settime"), // 227 + SYSCALL_WITH_UNKNOWN_ARGS("clock_gettime"), // 228 + SYSCALL_WITH_UNKNOWN_ARGS("clock_getres"), // 229 + SYSCALL_WITH_UNKNOWN_ARGS("clock_nanosleep"), // 230 + SYSCALL("exit_group", kInt), // 231 + SYSCALL_WITH_UNKNOWN_ARGS("epoll_wait"), // 232 + SYSCALL_WITH_UNKNOWN_ARGS("epoll_ctl"), // 233 + SYSCALL("tgkill", kInt, kInt, kSignal), // 234 + SYSCALL_WITH_UNKNOWN_ARGS("utimes"), // 235 + SYSCALL_WITH_UNKNOWN_ARGS("vserver"), // 236 + SYSCALL_WITH_UNKNOWN_ARGS("mbind"), // 237 + SYSCALL_WITH_UNKNOWN_ARGS("set_mempolicy"), // 238 + SYSCALL_WITH_UNKNOWN_ARGS("get_mempolicy"), // 239 + SYSCALL_WITH_UNKNOWN_ARGS("mq_open"), // 240 + SYSCALL_WITH_UNKNOWN_ARGS("mq_unlink"), // 241 + SYSCALL_WITH_UNKNOWN_ARGS("mq_timedsend"), // 242 + SYSCALL_WITH_UNKNOWN_ARGS("mq_timedreceive"), // 243 + SYSCALL_WITH_UNKNOWN_ARGS("mq_notify"), // 244 + SYSCALL_WITH_UNKNOWN_ARGS("mq_getsetattr"), // 245 + SYSCALL_WITH_UNKNOWN_ARGS("kexec_load"), // 246 + SYSCALL_WITH_UNKNOWN_ARGS("waitid"), // 247 + SYSCALL_WITH_UNKNOWN_ARGS("add_key"), // 248 + SYSCALL_WITH_UNKNOWN_ARGS("request_key"), // 249 + SYSCALL_WITH_UNKNOWN_ARGS("keyctl"), // 250 + SYSCALL_WITH_UNKNOWN_ARGS("ioprio_set"), // 251 + SYSCALL_WITH_UNKNOWN_ARGS("ioprio_get"), // 252 + SYSCALL_WITH_UNKNOWN_ARGS("inotify_init"), // 253 + SYSCALL_WITH_UNKNOWN_ARGS("inotify_add_watch"), // 254 + SYSCALL_WITH_UNKNOWN_ARGS("inotify_rm_watch"), // 255 + SYSCALL_WITH_UNKNOWN_ARGS("migrate_pages"), // 256 + SYSCALL("openat", kGen, kPath, kOct, kHex), // 257 + SYSCALL("mkdirat", kGen, kPath), // 258 + SYSCALL("mknodat", kGen, kPath), // 259 + SYSCALL("fchownat", kGen, kPath), // 260 + SYSCALL("futimesat", kGen, kPath), // 261 + SYSCALL("newfstatat", kGen, kPath), // 262 + SYSCALL("unlinkat", kGen, kPath), // 263 + SYSCALL("renameat", kGen, kPath, kGen, kPath), // 264 + SYSCALL("linkat", kGen, kPath, kGen, kPath), // 265 + SYSCALL("symlinkat", kPath, kGen, kPath), // 266 + SYSCALL("readlinkat", kGen, kPath), // 267 + SYSCALL("fchmodat", kGen, kPath), // 268 + SYSCALL("faccessat", kGen, kPath), // 269 + SYSCALL_WITH_UNKNOWN_ARGS("pselect6"), // 270 + SYSCALL_WITH_UNKNOWN_ARGS("ppoll"), // 271 + SYSCALL_WITH_UNKNOWN_ARGS("unshare"), // 272 + SYSCALL("set_robust_list", kGen, kGen), // 273 + SYSCALL_WITH_UNKNOWN_ARGS("get_robust_list"), // 274 + SYSCALL_WITH_UNKNOWN_ARGS("splice"), // 275 + SYSCALL_WITH_UNKNOWN_ARGS("tee"), // 276 + SYSCALL_WITH_UNKNOWN_ARGS("sync_file_range"), // 277 + SYSCALL_WITH_UNKNOWN_ARGS("vmsplice"), // 278 + SYSCALL_WITH_UNKNOWN_ARGS("move_pages"), // 279 + SYSCALL_WITH_UNKNOWN_ARGS("utimensat"), // 280 + SYSCALL_WITH_UNKNOWN_ARGS("epoll_pwait"), // 281 + SYSCALL_WITH_UNKNOWN_ARGS("signalfd"), // 282 + SYSCALL_WITH_UNKNOWN_ARGS("timerfd_create"), // 283 + SYSCALL_WITH_UNKNOWN_ARGS("eventfd"), // 284 + SYSCALL_WITH_UNKNOWN_ARGS("fallocate"), // 285 + SYSCALL_WITH_UNKNOWN_ARGS("timerfd_settime"), // 286 + SYSCALL_WITH_UNKNOWN_ARGS("timerfd_gettime"), // 287 + SYSCALL_WITH_UNKNOWN_ARGS("accept4"), // 288 + SYSCALL_WITH_UNKNOWN_ARGS("signalfd4"), // 289 + SYSCALL_WITH_UNKNOWN_ARGS("eventfd2"), // 290 + SYSCALL_WITH_UNKNOWN_ARGS("epoll_create1"), // 291 + SYSCALL("dup3", kGen, kGen, kGen), // 292 + SYSCALL_WITH_UNKNOWN_ARGS("pipe2"), // 293 + SYSCALL_WITH_UNKNOWN_ARGS("inotify_init1"), // 294 + SYSCALL_WITH_UNKNOWN_ARGS("preadv"), // 295 + SYSCALL_WITH_UNKNOWN_ARGS("pwritev"), // 296 + SYSCALL_WITH_UNKNOWN_ARGS("rt_tgsigqueueinfo"), // 297 + SYSCALL_WITH_UNKNOWN_ARGS("perf_event_open"), // 298 + SYSCALL("recvmmsg", kInt, kHex, kHex, kHex), // 299 + SYSCALL("fanotify_init", kHex, kHex, kInt), // 300 + SYSCALL("fanotify_mark", kInt, kHex, kInt, kPath), // 301 + SYSCALL("prlimit64", kInt, kInt, kHex, kHex), // 302 + SYSCALL("name_to_handle_at", kInt, kGen, kHex, kHex, kHex), // 303 + SYSCALL("open_by_handle_at", kInt, kHex, kHex), // 304 + SYSCALL("clock_adjtime", kInt, kHex), // 305 + SYSCALL("syncfs", kInt), // 306 + SYSCALL("sendmmsg", kInt, kHex, kInt, kHex), // 307 + SYSCALL("setns", kInt, kHex), // 308 + SYSCALL("getcpu", kHex, kHex, kHex), // 309 + SYSCALL("process_vm_readv", kInt, kHex, kInt, kHex, kInt, kInt), // 310 + SYSCALL("process_vm_writev", kInt, kHex, kInt, kHex, kInt, kInt), // 311 + SYSCALL("kcmp", kInt, kInt, kInt, kHex, kHex), // 312 + SYSCALL("finit_module", kInt, kPath, kHex), // 313 + SYSCALL("sched_setattr", kGen, kGen, kGen, kGen, kGen, kGen), // 314 + SYSCALL("sched_getattr", kGen, kGen, kGen, kGen, kGen, kGen), // 315 + SYSCALL("renameat2", kGen, kPath, kGen, kPath, kGen, kGen), // 316 + SYSCALL("seccomp", kGen, kGen, kGen, kGen, kGen, kGen), // 317 + SYSCALL("getrandom", kGen, kGen, kGen, kGen, kGen, kGen), // 318 + SYSCALL("memfd_create", kGen, kGen, kGen, kGen, kGen, kGen), // 319 + SYSCALL("kexec_file_load", kGen, kGen, kGen, kGen, kGen, kGen), // 320 + SYSCALL("bpf", kHex, kHex, kHex, kHex, kHex, kHex), // 321 + SYSCALL("execveat", kHex, kPath, kHex, kHex, kHex), // 322 + SYSCALL("userfaultfd", kHex), // 323 + SYSCALL("membarrier", kHex, kHex), // 324 +}; + +const absl::Span SyscallTable::kSyscallDataX8632 = { + SYSCALL("restart_syscall", kHex, kHex, kHex, kHex, kHex, kHex), // 0 + SYSCALL("exit", kHex, kHex, kHex, kHex, kHex, kHex), // 1 + SYSCALL("fork", kHex, kHex, kHex, kHex, kHex, kHex), // 2 + SYSCALL("read", kHex, kHex, kHex, kHex, kHex, kHex), // 3 + SYSCALL("write", kHex, kHex, kHex, kHex, kHex, kHex), // 4 + SYSCALL("open", kPath, kHex, kOct, kHex, kHex, kHex), // 5 + SYSCALL("close", kHex, kHex, kHex, kHex, kHex, kHex), // 6 + SYSCALL("waitpid", kHex, kHex, kHex, kHex, kHex, kHex), // 7 + SYSCALL("creat", kPath, kHex, kHex, kHex, kHex, kHex), // 8 + SYSCALL("link", kPath, kPath, kHex, kHex, kHex, kHex), // 9 + SYSCALL("unlink", kPath, kHex, kHex, kHex, kHex, kHex), // 10 + SYSCALL("execve", kPath, kHex, kHex, kHex, kHex, kHex), // 11 + SYSCALL("chdir", kPath, kHex, kHex, kHex, kHex, kHex), // 12 + SYSCALL("time", kHex, kHex, kHex, kHex, kHex, kHex), // 13 + SYSCALL("mknod", kPath, kOct, kHex, kHex, kHex, kHex), // 14 + SYSCALL("chmod", kPath, kOct, kHex, kHex, kHex, kHex), // 15 + SYSCALL("lchown", kPath, kInt, kInt, kHex, kHex, kHex), // 16 + SYSCALL("break", kHex, kHex, kHex, kHex, kHex, kHex), // 17 + SYSCALL("oldstat", kHex, kHex, kHex, kHex, kHex, kHex), // 18 + SYSCALL("lseek", kHex, kHex, kHex, kHex, kHex, kHex), // 19 + SYSCALL("getpid", kHex, kHex, kHex, kHex, kHex, kHex), // 20 + SYSCALL("mount", kHex, kHex, kHex, kHex, kHex, kHex), // 21 + SYSCALL("umount", kHex, kHex, kHex, kHex, kHex, kHex), // 22 + SYSCALL("setuid", kHex, kHex, kHex, kHex, kHex, kHex), // 23 + SYSCALL("getuid", kHex, kHex, kHex, kHex, kHex, kHex), // 24 + SYSCALL("stime", kHex, kHex, kHex, kHex, kHex, kHex), // 25 + SYSCALL("ptrace", kHex, kHex, kHex, kHex), // 26 + SYSCALL("alarm", kHex, kHex, kHex, kHex, kHex, kHex), // 27 + SYSCALL("oldfstat", kHex, kHex, kHex, kHex, kHex, kHex), // 28 + SYSCALL("pause", kHex, kHex, kHex, kHex, kHex, kHex), // 29 + SYSCALL("utime", kHex, kHex, kHex, kHex, kHex, kHex), // 30 + SYSCALL("stty", kHex, kHex, kHex, kHex, kHex, kHex), // 31 + SYSCALL("gtty", kHex, kHex, kHex, kHex, kHex, kHex), // 32 + SYSCALL("access", kPath, kHex, kHex, kHex, kHex, kHex), // 33 + SYSCALL("nice", kHex, kHex, kHex, kHex, kHex, kHex), // 34 + SYSCALL("ftime", kHex, kHex, kHex, kHex, kHex, kHex), // 35 + SYSCALL("sync", kHex, kHex, kHex, kHex, kHex, kHex), // 36 + SYSCALL("kill", kHex, kHex, kHex, kHex, kHex, kHex), // 37 + SYSCALL("rename", kPath, kPath, kHex, kHex, kHex, kHex), // 38 + SYSCALL("mkdir", kPath, kHex, kHex, kHex, kHex, kHex), // 39 + SYSCALL("rmdir", kHex, kHex, kHex, kHex, kHex, kHex), // 40 + SYSCALL("dup", kHex, kHex, kHex, kHex, kHex, kHex), // 41 + SYSCALL("pipe", kHex, kHex, kHex, kHex, kHex, kHex), // 42 + SYSCALL("times", kHex, kHex, kHex, kHex, kHex, kHex), // 43 + SYSCALL("prof", kHex, kHex, kHex, kHex, kHex, kHex), // 44 + SYSCALL("brk", kHex, kHex, kHex, kHex, kHex, kHex), // 45 + SYSCALL("setgid", kHex, kHex, kHex, kHex, kHex, kHex), // 46 + SYSCALL("getgid", kHex, kHex, kHex, kHex, kHex, kHex), // 47 + SYSCALL("signal", kHex, kHex, kHex, kHex, kHex, kHex), // 48 + SYSCALL("geteuid", kHex, kHex, kHex, kHex, kHex, kHex), // 49 + SYSCALL("getegid", kHex, kHex, kHex, kHex, kHex, kHex), // 50 + SYSCALL("acct", kHex, kHex, kHex, kHex, kHex, kHex), // 51 + SYSCALL("umount2", kHex, kHex, kHex, kHex, kHex, kHex), // 52 + SYSCALL("lock", kHex, kHex, kHex, kHex, kHex, kHex), // 53 + SYSCALL("ioctl", kHex, kHex, kHex, kHex, kHex, kHex), // 54 + SYSCALL("fcntl", kHex, kHex, kHex, kHex, kHex, kHex), // 55 + SYSCALL("mpx", kHex, kHex, kHex, kHex, kHex, kHex), // 56 + SYSCALL("setpgid", kHex, kHex, kHex, kHex, kHex, kHex), // 57 + SYSCALL("ulimit", kHex, kHex, kHex, kHex, kHex, kHex), // 58 + SYSCALL("oldolduname", kHex, kHex, kHex, kHex, kHex, kHex), // 59 + SYSCALL("umask", kHex, kHex, kHex, kHex, kHex, kHex), // 60 + SYSCALL("chroot", kHex, kHex, kHex, kHex, kHex, kHex), // 61 + SYSCALL("ustat", kHex, kHex, kHex, kHex, kHex, kHex), // 62 + SYSCALL("dup2", kHex, kHex, kHex, kHex, kHex, kHex), // 63 + SYSCALL("getppid", kHex, kHex, kHex, kHex, kHex, kHex), // 64 + SYSCALL("getpgrp", kHex, kHex, kHex, kHex, kHex, kHex), // 65 + SYSCALL("setsid", kHex, kHex, kHex, kHex, kHex, kHex), // 66 + SYSCALL("sigaction", kHex, kHex, kHex, kHex, kHex, kHex), // 67 + SYSCALL("sgetmask", kHex, kHex, kHex, kHex, kHex, kHex), // 68 + SYSCALL("ssetmask", kHex, kHex, kHex, kHex, kHex, kHex), // 69 + SYSCALL("setreuid", kHex, kHex, kHex, kHex, kHex, kHex), // 70 + SYSCALL("setregid", kHex, kHex, kHex, kHex, kHex, kHex), // 71 + SYSCALL("sigsuspend", kHex, kHex, kHex, kHex, kHex, kHex), // 72 + SYSCALL("sigpending", kHex, kHex, kHex, kHex, kHex, kHex), // 73 + SYSCALL("sethostname", kHex, kHex, kHex, kHex, kHex, kHex), // 74 + SYSCALL("setrlimit", kHex, kHex, kHex, kHex, kHex, kHex), // 75 + SYSCALL("getrlimit", kHex, kHex, kHex, kHex, kHex, kHex), // 76 + SYSCALL("getrusage", kHex, kHex, kHex, kHex, kHex, kHex), // 77 + SYSCALL("gettimeofday", kHex, kHex, kHex, kHex, kHex, kHex), // 78 + SYSCALL("settimeofday", kHex, kHex, kHex, kHex, kHex, kHex), // 79 + SYSCALL("getgroups", kHex, kHex, kHex, kHex, kHex, kHex), // 80 + SYSCALL("setgroups", kHex, kHex, kHex, kHex, kHex, kHex), // 81 + SYSCALL("select", kHex, kHex, kHex, kHex, kHex, kHex), // 82 + SYSCALL("symlink", kPath, kPath, kHex, kHex, kHex, kHex), // 83 + SYSCALL("oldlstat", kHex, kHex, kHex, kHex, kHex, kHex), // 84 + SYSCALL("readlink", kPath, kHex, kInt, kHex, kHex, kHex), // 85 + SYSCALL("uselib", kPath, kHex, kHex, kHex, kHex, kHex), // 86 + SYSCALL("swapon", kHex, kHex, kHex, kHex, kHex, kHex), // 87 + SYSCALL("reboot", kHex, kHex, kHex, kHex, kHex, kHex), // 88 + SYSCALL("readdir", kHex, kHex, kHex, kHex, kHex, kHex), // 89 + SYSCALL("mmap", kHex, kHex, kHex, kHex, kHex, kHex), // 90 + SYSCALL("munmap", kHex, kHex, kHex, kHex, kHex, kHex), // 91 + SYSCALL("truncate", kPath, kHex, kHex, kHex, kHex, kHex), // 92 + SYSCALL("ftruncate", kHex, kHex, kHex, kHex, kHex, kHex), // 93 + SYSCALL("fchmod", kHex, kHex, kHex, kHex, kHex, kHex), // 94 + SYSCALL("fchown", kHex, kHex, kHex, kHex, kHex, kHex), // 95 + SYSCALL("getpriority", kHex, kHex, kHex, kHex, kHex, kHex), // 96 + SYSCALL("setpriority", kHex, kHex, kHex, kHex, kHex, kHex), // 97 + SYSCALL("profil", kHex, kHex, kHex, kHex, kHex, kHex), // 98 + SYSCALL("statfs", kPath, kHex, kHex, kHex, kHex, kHex), // 99 + SYSCALL("fstatfs", kHex, kHex, kHex, kHex, kHex, kHex), // 100 + SYSCALL("ioperm", kHex, kHex, kHex, kHex, kHex, kHex), // 101 + SYSCALL("socketcall", kHex, kHex, kHex, kHex, kHex, kHex), // 102 + SYSCALL("syslog", kHex, kHex, kHex, kHex, kHex, kHex), // 103 + SYSCALL("setitimer", kHex, kHex, kHex, kHex, kHex, kHex), // 104 + SYSCALL("getitimer", kHex, kHex, kHex, kHex, kHex, kHex), // 105 + SYSCALL("stat", kPath, kHex, kHex, kHex, kHex, kHex), // 106 + SYSCALL("lstat", kPath, kHex, kHex, kHex, kHex, kHex), // 107 + SYSCALL("fstat", kHex, kHex, kHex, kHex, kHex, kHex), // 108 + SYSCALL("olduname", kHex, kHex, kHex, kHex, kHex, kHex), // 109 + SYSCALL("iopl", kHex, kHex, kHex, kHex, kHex, kHex), // 110 + SYSCALL("vhangup", kHex, kHex, kHex, kHex, kHex, kHex), // 111 + SYSCALL("idle", kHex, kHex, kHex, kHex, kHex, kHex), // 112 + SYSCALL("vm86old", kHex, kHex, kHex, kHex, kHex, kHex), // 113 + SYSCALL("wait4", kHex, kHex, kHex, kHex, kHex, kHex), // 114 + SYSCALL("swapoff", kHex, kHex, kHex, kHex, kHex, kHex), // 115 + SYSCALL("sysinfo", kHex, kHex, kHex, kHex, kHex, kHex), // 116 + SYSCALL("ipc", kHex, kHex, kHex, kHex, kHex, kHex), // 117 + SYSCALL("fsync", kHex, kHex, kHex, kHex, kHex, kHex), // 118 + SYSCALL("sigreturn", kHex, kHex, kHex, kHex, kHex, kHex), // 119 + SYSCALL("clone", kHex, kHex, kHex, kHex, kHex, kHex), // 120 + SYSCALL("setdomainname", kHex, kHex, kHex, kHex, kHex, kHex), // 121 + SYSCALL("uname", kHex, kHex, kHex, kHex, kHex, kHex), // 122 + SYSCALL("modify_ldt", kHex, kHex, kHex, kHex, kHex, kHex), // 123 + SYSCALL("adjtimex", kHex, kHex, kHex, kHex, kHex, kHex), // 124 + SYSCALL("mprotect", kHex, kHex, kHex, kHex, kHex, kHex), // 125 + SYSCALL("sigprocmask", kHex, kHex, kHex, kHex, kHex, kHex), // 126 + SYSCALL("create_module", kHex, kHex, kHex, kHex, kHex, kHex), // 127 + SYSCALL("init_module", kHex, kHex, kHex, kHex, kHex, kHex), // 128 + SYSCALL("delete_module", kHex, kHex, kHex, kHex, kHex, kHex), // 129 + SYSCALL("get_kernel_syms", kHex, kHex, kHex, kHex, kHex, kHex), // 130 + SYSCALL("quotactl", kHex, kHex, kHex, kHex, kHex, kHex), // 131 + SYSCALL("getpgid", kHex, kHex, kHex, kHex, kHex, kHex), // 132 + SYSCALL("fchdir", kHex, kHex, kHex, kHex, kHex, kHex), // 133 + SYSCALL("bdflush", kHex, kHex, kHex, kHex, kHex, kHex), // 134 + SYSCALL("sysfs", kHex, kHex, kHex, kHex, kHex, kHex), // 135 + SYSCALL("personality", kHex, kHex, kHex, kHex, kHex, kHex), // 136 + SYSCALL("afs_syscall", kHex, kHex, kHex, kHex, kHex, kHex), // 137 + SYSCALL("setfsuid", kHex, kHex, kHex, kHex, kHex, kHex), // 138 + SYSCALL("setfsgid", kHex, kHex, kHex, kHex, kHex, kHex), // 139 + SYSCALL("_llseek", kHex, kHex, kHex, kHex, kHex, kHex), // 140 + SYSCALL("getdents", kHex, kHex, kHex, kHex, kHex, kHex), // 141 + SYSCALL("_newselect", kHex, kHex, kHex, kHex, kHex, kHex), // 142 + SYSCALL("flock", kHex, kHex, kHex, kHex, kHex, kHex), // 143 + SYSCALL("msync", kHex, kHex, kHex, kHex, kHex, kHex), // 144 + SYSCALL("readv", kHex, kHex, kHex, kHex, kHex, kHex), // 145 + SYSCALL("writev", kHex, kHex, kHex, kHex, kHex, kHex), // 146 + SYSCALL("getsid", kHex, kHex, kHex, kHex, kHex, kHex), // 147 + SYSCALL("fdatasync", kHex, kHex, kHex, kHex, kHex, kHex), // 148 + SYSCALL("_sysctl", kHex, kHex, kHex, kHex, kHex, kHex), // 149 + SYSCALL("mlock", kHex, kHex, kHex, kHex, kHex, kHex), // 150 + SYSCALL("munlock", kHex, kHex, kHex, kHex, kHex, kHex), // 151 + SYSCALL("mlockall", kHex, kHex, kHex, kHex, kHex, kHex), // 152 + SYSCALL("munlockall", kHex, kHex, kHex, kHex, kHex, kHex), // 153 + SYSCALL("sched_setparam", kHex, kHex, kHex, kHex, kHex, kHex), // 154 + SYSCALL("sched_getparam", kHex, kHex, kHex, kHex, kHex, kHex), // 155 + SYSCALL("sched_setscheduler", kHex, kHex, kHex, kHex, kHex, kHex), // 156 + SYSCALL("sched_getscheduler", kHex, kHex, kHex, kHex, kHex, kHex), // 157 + SYSCALL("sched_yield", kHex, kHex, kHex, kHex, kHex, kHex), // 158 + SYSCALL("sched_get_priority_max", kHex, kHex, kHex, kHex, kHex, + kHex), // 159 + SYSCALL("sched_get_priority_min", kHex, kHex, kHex, kHex, kHex, + kHex), // 160 + SYSCALL("sched_rr_get_interval", kHex, kHex, kHex, kHex, kHex, + kHex), // 161 + SYSCALL("nanosleep", kHex, kHex, kHex, kHex, kHex, kHex), // 162 + SYSCALL("mremap", kHex, kHex, kHex, kHex, kHex, kHex), // 163 + SYSCALL("setresuid", kHex, kHex, kHex, kHex, kHex, kHex), // 164 + SYSCALL("getresuid", kHex, kHex, kHex, kHex, kHex, kHex), // 165 + SYSCALL("vm86", kHex, kHex, kHex, kHex, kHex, kHex), // 166 + SYSCALL("query_module", kHex, kHex, kHex, kHex, kHex, kHex), // 167 + SYSCALL("poll", kHex, kHex, kHex, kHex, kHex, kHex), // 168 + SYSCALL("nfsservctl", kHex, kHex, kHex, kHex, kHex, kHex), // 169 + SYSCALL("setresgid", kHex, kHex, kHex, kHex, kHex, kHex), // 170 + SYSCALL("getresgid", kHex, kHex, kHex, kHex, kHex, kHex), // 171 + SYSCALL("prctl", kHex, kHex, kHex, kHex, kHex, kHex), // 172 + SYSCALL("rt_sigreturn", kHex, kHex, kHex, kHex, kHex, kHex), // 173 + SYSCALL("rt_sigaction", kHex, kHex, kHex, kHex, kHex, kHex), // 174 + SYSCALL("rt_sigprocmask", kHex, kHex, kHex, kHex, kHex, kHex), // 175 + SYSCALL("rt_sigpending", kHex, kHex, kHex, kHex, kHex, kHex), // 176 + SYSCALL("rt_sigtimedwait", kHex, kHex, kHex, kHex, kHex, kHex), // 177 + SYSCALL("rt_sigqueueinfo", kHex, kHex, kHex, kHex, kHex, kHex), // 178 + SYSCALL("rt_sigsuspend", kHex, kHex, kHex, kHex, kHex, kHex), // 179 + SYSCALL("pread64", kHex, kHex, kHex, kHex, kHex, kHex), // 180 + SYSCALL("pwrite64", kHex, kHex, kHex, kHex, kHex, kHex), // 181 + SYSCALL("chown", kHex, kHex, kHex, kHex, kHex, kHex), // 182 + SYSCALL("getcwd", kHex, kHex, kHex, kHex, kHex, kHex), // 183 + SYSCALL("capget", kHex, kHex, kHex, kHex, kHex, kHex), // 184 + SYSCALL("capset", kHex, kHex, kHex, kHex, kHex, kHex), // 185 + SYSCALL("sigaltstack", kHex, kHex, kHex, kHex, kHex, kHex), // 186 + SYSCALL("sendfile", kHex, kHex, kHex, kHex, kHex, kHex), // 187 + SYSCALL("getpmsg", kHex, kHex, kHex, kHex, kHex, kHex), // 188 + SYSCALL("putpmsg", kHex, kHex, kHex, kHex, kHex, kHex), // 189 + SYSCALL("vfork", kHex, kHex, kHex, kHex, kHex, kHex), // 190 + SYSCALL("ugetrlimit", kHex, kHex, kHex, kHex, kHex, kHex), // 191 + SYSCALL("mmap2", kHex, kHex, kHex, kHex, kHex, kHex), // 192 + SYSCALL("truncate64", kPath, kHex, kHex, kHex, kHex, kHex), // 193 + SYSCALL("ftruncate64", kHex, kHex, kHex, kHex, kHex, kHex), // 194 + SYSCALL("stat64", kHex, kHex, kHex, kHex, kHex, kHex), // 195 + SYSCALL("lstat64", kPath, kHex, kHex, kHex, kHex, kHex), // 196 + SYSCALL("fstat64", kHex, kHex, kHex, kHex, kHex, kHex), // 197 + SYSCALL("lchown32", kHex, kHex, kHex, kHex, kHex, kHex), // 198 + SYSCALL("getuid32", kHex, kHex, kHex, kHex, kHex, kHex), // 199 + SYSCALL("getgid32", kHex, kHex, kHex, kHex, kHex, kHex), // 200 + SYSCALL("geteuid32", kHex, kHex, kHex, kHex, kHex, kHex), // 201 + SYSCALL("getegid32", kHex, kHex, kHex, kHex, kHex, kHex), // 202 + SYSCALL("setreuid32", kHex, kHex, kHex, kHex, kHex, kHex), // 203 + SYSCALL("setregid32", kHex, kHex, kHex, kHex, kHex, kHex), // 204 + SYSCALL("getgroups32", kHex, kHex, kHex, kHex, kHex, kHex), // 205 + SYSCALL("setgroups32", kHex, kHex, kHex, kHex, kHex, kHex), // 206 + SYSCALL("fchown32", kHex, kHex, kHex, kHex, kHex, kHex), // 207 + SYSCALL("setresuid32", kHex, kHex, kHex, kHex, kHex, kHex), // 208 + SYSCALL("getresuid32", kHex, kHex, kHex, kHex, kHex, kHex), // 209 + SYSCALL("setresgid32", kHex, kHex, kHex, kHex, kHex, kHex), // 210 + SYSCALL("getresgid32", kHex, kHex, kHex, kHex, kHex, kHex), // 211 + SYSCALL("chown32", kHex, kHex, kHex, kHex, kHex, kHex), // 212 + SYSCALL("setuid32", kHex, kHex, kHex, kHex, kHex, kHex), // 213 + SYSCALL("setgid32", kHex, kHex, kHex, kHex, kHex, kHex), // 214 + SYSCALL("setfsuid32", kHex, kHex, kHex, kHex, kHex, kHex), // 215 + SYSCALL("setfsgid32", kHex, kHex, kHex, kHex, kHex, kHex), // 216 + SYSCALL("pivot_root", kHex, kHex, kHex, kHex, kHex, kHex), // 217 + SYSCALL("mincore", kHex, kHex, kHex, kHex, kHex, kHex), // 218 + SYSCALL("madvise", kHex, kHex, kHex, kHex, kHex, kHex), // 219 + SYSCALL("getdents64", kHex, kHex, kHex, kHex, kHex, kHex), // 220 + SYSCALL("fcntl64", kHex, kHex, kHex, kHex, kHex, kHex), // 221 + SYSCALL("unused1-222", kHex, kHex, kHex, kHex, kHex, kHex), // 222 + SYSCALL("unused2-223", kHex, kHex, kHex, kHex, kHex, kHex), // 223 + SYSCALL("gettid", kHex, kHex, kHex, kHex, kHex, kHex), // 224 + SYSCALL("readahead", kHex, kHex, kHex, kHex, kHex, kHex), // 225 + SYSCALL("setxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 226 + SYSCALL("lsetxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 227 + SYSCALL("fsetxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 228 + SYSCALL("getxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 229 + SYSCALL("lgetxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 230 + SYSCALL("fgetxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 231 + SYSCALL("listxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 232 + SYSCALL("llistxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 233 + SYSCALL("flistxattr", kHex, kHex, kHex, kHex, kHex, kHex), // 234 + SYSCALL("removexattr", kHex, kHex, kHex, kHex, kHex, kHex), // 235 + SYSCALL("lremovexattr", kHex, kHex, kHex, kHex, kHex, kHex), // 236 + SYSCALL("fremovexattr", kHex, kHex, kHex, kHex, kHex, kHex), // 237 + SYSCALL("tkill", kHex, kHex, kHex, kHex, kHex, kHex), // 238 + SYSCALL("sendfile64", kHex, kHex, kHex, kHex, kHex, kHex), // 239 + SYSCALL("futex", kHex, kHex, kHex, kHex, kHex, kHex), // 240 + SYSCALL("sched_setaffinity", kHex, kHex, kHex, kHex, kHex, kHex), // 241 + SYSCALL("sched_getaffinity", kHex, kHex, kHex, kHex, kHex, kHex), // 242 + SYSCALL("set_thread_area", kHex, kHex, kHex, kHex, kHex, kHex), // 243 + SYSCALL("get_thread_area", kHex, kHex, kHex, kHex, kHex, kHex), // 244 + SYSCALL("io_setup", kHex, kHex, kHex, kHex, kHex, kHex), // 245 + SYSCALL("io_destroy", kHex, kHex, kHex, kHex, kHex, kHex), // 246 + SYSCALL("io_getevents", kHex, kHex, kHex, kHex, kHex, kHex), // 247 + SYSCALL("io_submit", kHex, kHex, kHex, kHex, kHex, kHex), // 248 + SYSCALL("io_cancel", kHex, kHex, kHex, kHex, kHex, kHex), // 249 + SYSCALL("fadvise64", kHex, kHex, kHex, kHex, kHex, kHex), // 250 + SYSCALL("251-old_sys_set_zone_reclaim", kHex, kHex, kHex, kHex, kHex, + kHex), // 251 + SYSCALL("exit_group", kHex, kHex, kHex, kHex, kHex, kHex), // 252 + SYSCALL("lookup_dcookie", kHex, kHex, kHex, kHex, kHex, kHex), // 253 + SYSCALL("epoll_create", kHex, kHex, kHex, kHex, kHex, kHex), // 254 + SYSCALL("epoll_ctl", kHex, kHex, kHex, kHex, kHex, kHex), // 255 + SYSCALL("epoll_wait", kHex, kHex, kHex, kHex, kHex, kHex), // 256 + SYSCALL("remap_file_pages", kHex, kHex, kHex, kHex, kHex, kHex), // 257 + SYSCALL("set_tid_address", kHex, kHex, kHex, kHex, kHex, kHex), // 258 + SYSCALL("timer_create", kHex, kHex, kHex, kHex, kHex, kHex), // 259 + SYSCALL("timer_settime", kHex, kHex, kHex, kHex, kHex, kHex), // 260 + SYSCALL("timer_gettime", kHex, kHex, kHex, kHex, kHex, kHex), // 261 + SYSCALL("timer_getoverrun", kHex, kHex, kHex, kHex, kHex, kHex), // 262 + SYSCALL("timer_delete", kHex, kHex, kHex, kHex, kHex, kHex), // 263 + SYSCALL("clock_settime", kHex, kHex, kHex, kHex, kHex, kHex), // 264 + SYSCALL("clock_gettime", kHex, kHex, kHex, kHex, kHex, kHex), // 265 + SYSCALL("clock_getres", kHex, kHex, kHex, kHex, kHex, kHex), // 266 + SYSCALL("clock_nanosleep", kHex, kHex, kHex, kHex, kHex, kHex), // 267 + SYSCALL("statfs64", kHex, kHex, kHex, kHex, kHex, kHex), // 268 + SYSCALL("fstatfs64", kHex, kHex, kHex, kHex, kHex, kHex), // 269 + SYSCALL("tgkill", kHex, kHex, kHex, kHex, kHex, kHex), // 270 + SYSCALL("utimes", kHex, kHex, kHex, kHex, kHex, kHex), // 271 + SYSCALL("fadvise64_64", kHex, kHex, kHex, kHex, kHex, kHex), // 272 + SYSCALL("vserver", kHex, kHex, kHex, kHex, kHex, kHex), // 273 + SYSCALL("mbind", kHex, kHex, kHex, kHex, kHex, kHex), // 274 + SYSCALL("get_mempolicy", kHex, kHex, kHex, kHex, kHex, kHex), // 275 + SYSCALL("set_mempolicy", kHex, kHex, kHex, kHex, kHex, kHex), // 276 + SYSCALL("mq_open", kHex, kHex, kHex, kHex, kHex, kHex), // 277 + SYSCALL("mq_unlink", kHex, kHex, kHex, kHex, kHex, kHex), // 278 + SYSCALL("mq_timedsend", kHex, kHex, kHex, kHex, kHex, kHex), // 279 + SYSCALL("mq_timedreceive", kHex, kHex, kHex, kHex, kHex, kHex), // 280 + SYSCALL("mq_notify", kHex, kHex, kHex, kHex, kHex, kHex), // 281 + SYSCALL("mq_getsetattr", kHex, kHex, kHex, kHex, kHex, kHex), // 282 + SYSCALL("kexec_load", kHex, kHex, kHex, kHex, kHex, kHex), // 283 + SYSCALL("waitid", kHex, kHex, kHex, kHex, kHex, kHex), // 284 + SYSCALL("285-old_sys_setaltroot", kHex, kHex, kHex, kHex, kHex, + kHex), // 285 + SYSCALL("add_key", kHex, kHex, kHex, kHex, kHex, kHex), // 286 + SYSCALL("request_key", kHex, kHex, kHex, kHex, kHex, kHex), // 287 + SYSCALL("keyctl", kHex, kHex, kHex, kHex, kHex, kHex), // 288 + SYSCALL("ioprio_set", kHex, kHex, kHex, kHex, kHex, kHex), // 289 + SYSCALL("ioprio_get", kHex, kHex, kHex, kHex, kHex, kHex), // 290 + SYSCALL("inotify_init", kHex, kHex, kHex, kHex, kHex, kHex), // 291 + SYSCALL("inotify_add_watch", kHex, kHex, kHex, kHex, kHex, kHex), // 292 + SYSCALL("inotify_rm_watch", kHex, kHex, kHex, kHex, kHex, kHex), // 293 + SYSCALL("migrate_pages", kHex, kHex, kHex, kHex, kHex, kHex), // 294 + SYSCALL("openat", kHex, kPath, kOct, kHex, kHex, kHex), // 295 + SYSCALL("mkdirat", kHex, kHex, kHex, kHex, kHex, kHex), // 296 + SYSCALL("mknodat", kHex, kHex, kHex, kHex, kHex, kHex), // 297 + SYSCALL("fchownat", kHex, kPath, kHex, kHex, kHex, kHex), // 298 + SYSCALL("futimesat", kHex, kPath, kHex, kHex, kHex, kHex), // 299 + SYSCALL("fstatat64", kHex, kHex, kHex, kHex, kHex, kHex), // 300 + SYSCALL("unlinkat", kHex, kPath, kHex, kHex, kHex, kHex), // 301 + SYSCALL("renameat", kHex, kPath, kHex, kPath, kHex, kHex), // 302 + SYSCALL("linkat", kHex, kPath, kHex, kPath, kHex, kHex), // 303 + SYSCALL("symlinkat", kPath, kHex, kPath, kHex, kHex, kHex), // 304 + SYSCALL("readlinkat", kHex, kPath, kHex, kHex, kHex, kHex), // 305 + SYSCALL("fchmodat", kHex, kPath, kHex, kHex, kHex, kHex), // 306 + SYSCALL("faccessat", kHex, kPath, kHex, kHex, kHex, kHex), // 307 + SYSCALL("pselect6", kHex, kHex, kHex, kHex, kHex, kHex), // 308 + SYSCALL("ppoll", kHex, kHex, kHex, kHex, kHex, kHex), // 309 + SYSCALL("unshare", kHex, kHex, kHex, kHex, kHex, kHex), // 310 + SYSCALL("set_robust_list", kHex, kHex, kHex, kHex, kHex, kHex), // 311 + SYSCALL("get_robust_list", kHex, kHex, kHex, kHex, kHex, kHex), // 312 + SYSCALL("splice", kHex, kHex, kHex, kHex, kHex, kHex), // 313 + SYSCALL("sync_file_range", kHex, kHex, kHex, kHex, kHex, kHex), // 314 + SYSCALL("tee", kHex, kHex, kHex, kHex, kHex, kHex), // 315 + SYSCALL("vmsplice", kHex, kHex, kHex, kHex, kHex, kHex), // 316 + SYSCALL("move_pages", kHex, kHex, kHex, kHex, kHex, kHex), // 317 + SYSCALL("getcpu", kHex, kHex, kHex, kHex, kHex, kHex), // 318 + SYSCALL("epoll_pwait", kHex, kHex, kHex, kHex, kHex, kHex), // 319 + SYSCALL("utimensat", kHex, kHex, kHex, kHex, kHex, kHex), // 320 + SYSCALL("signalfd", kHex, kHex, kHex, kHex, kHex, kHex), // 321 + SYSCALL("timerfd_create", kHex, kHex, kHex, kHex, kHex, kHex), // 322 + SYSCALL("eventfd", kHex, kHex, kHex, kHex, kHex, kHex), // 323 + SYSCALL("fallocate", kHex, kHex, kHex, kHex, kHex, kHex), // 324 + SYSCALL("timerfd_settime", kHex, kHex, kHex, kHex, kHex, kHex), // 325 + SYSCALL("timerfd_gettime", kHex, kHex, kHex, kHex, kHex, kHex), // 326 + SYSCALL("signalfd4", kHex, kHex, kHex, kHex, kHex, kHex), // 327 + SYSCALL("eventfd2", kHex, kHex, kHex, kHex, kHex, kHex), // 328 + SYSCALL("epoll_create1", kHex, kHex, kHex, kHex, kHex, kHex), // 329 + SYSCALL("dup3", kHex, kHex, kHex, kHex, kHex, kHex), // 330 + SYSCALL("pipe2", kHex, kHex, kHex, kHex, kHex, kHex), // 331 + SYSCALL("inotify_init1", kHex, kHex, kHex, kHex, kHex, kHex), // 332 + SYSCALL("preadv", kHex, kHex, kHex, kHex, kHex, kHex), // 333 + SYSCALL("pwritev", kHex, kHex, kHex, kHex, kHex, kHex), // 334 + SYSCALL("rt_tgsigqueueinfo", kHex, kHex, kHex, kHex, kHex, kHex), // 335 + SYSCALL("perf_event_open", kHex, kHex, kHex, kHex, kHex, kHex), // 336 + SYSCALL("recvmmsg", kHex, kHex, kHex, kHex, kHex, kHex), // 337 + SYSCALL("fanotify_init", kHex, kHex, kHex, kHex, kHex, kHex), // 338 + SYSCALL("fanotify_mark", kHex, kHex, kHex, kHex, kHex, kHex), // 339 + SYSCALL("prlimit64", kHex, kHex, kHex, kHex, kHex, kHex), // 340 + SYSCALL("name_to_handle_at", kHex, kHex, kHex, kHex, kHex, kHex), // 341 + SYSCALL("open_by_handle_at", kHex, kHex, kHex, kHex, kHex, kHex), // 342 + SYSCALL("clock_adjtime", kHex, kHex, kHex, kHex, kHex, kHex), // 343 + SYSCALL("syncfs", kHex, kHex, kHex, kHex, kHex, kHex), // 344 + SYSCALL("sendmmsg", kHex, kHex, kHex, kHex, kHex, kHex), // 345 + SYSCALL("setns", kHex, kHex, kHex, kHex, kHex, kHex), // 346 + SYSCALL("process_vm_readv", kHex, kHex, kHex, kHex, kHex, kHex), // 347 + SYSCALL("process_vm_writev", kHex, kHex, kHex, kHex, kHex, kHex), // 348 + SYSCALL("kcmp", kHex, kHex, kHex, kHex, kHex, kHex), // 349 + SYSCALL("finit_module", kHex, kHex, kHex, kHex, kHex, kHex), // 350 + SYSCALL("sched_setattr", kHex, kHex, kHex, kHex, kHex, kHex), // 351 + SYSCALL("sched_getattr", kHex, kHex, kHex, kHex, kHex, kHex), // 352 + SYSCALL("renameat2", kHex, kPath, kHex, kPath, kHex, kHex), // 353 + SYSCALL("seccomp", kHex, kHex, kHex, kHex, kHex, kHex), // 354 + SYSCALL("getrandom", kHex, kHex, kHex, kHex, kHex, kHex), // 355 + SYSCALL("memfd_create", kHex, kHex, kHex, kHex, kHex, kHex), // 356 + SYSCALL("bpf", kHex, kHex, kHex, kHex, kHex, kHex), // 357 +}; + +#elif defined(__powerpc64__) + +// http://lxr.free-electrons.com/source/arch/powerpc/include/uapi/asm/unistd.h +// Note: PPC64 syscalls can have up to 7 register arguments, but nobody is +// using the 7th argument - probably for x64 compatibility reasons. +const absl::Span SyscallTable::kSyscallDataPPC64 = { + SYSCALL("restart_syscall", kGen, kGen, kGen, kGen, kGen, kGen), // 0 + SYSCALL("exit", kInt, kGen, kGen, kGen, kGen, kGen), // 1 + SYSCALL("fork", kGen, kGen, kGen, kGen, kGen, kGen), // 2 + SYSCALL("read", kInt, kHex, kInt), // 3 + SYSCALL("write", kInt, kHex, kInt, kGen, kGen, kGen), // 4 + SYSCALL("open", kPath, kHex, kOct, kGen, kGen, kGen), // 5 + SYSCALL("close", kInt, kGen, kGen, kGen, kGen, kGen), // 6 + SYSCALL("waitpid", kHex, kHex, kHex, kHex, kHex, kHex), // 7 + SYSCALL("creat", kPath, kOct, kGen, kGen, kGen, kGen), // 8 + SYSCALL("link", kPath, kPath, kGen, kGen, kGen, kGen), // 9 + SYSCALL("unlink", kPath, kGen, kGen, kGen, kGen, kGen), // 10 + SYSCALL("execve", kPath, kHex, kHex, kGen, kGen, kGen), // 11 + SYSCALL("chdir", kPath, kGen, kGen, kGen, kGen, kGen), // 12 + SYSCALL("time", kHex, kGen, kGen, kGen, kGen, kGen), // 13 + SYSCALL("mknod", kPath, kOct, kHex, kGen, kGen, kGen), // 14 + SYSCALL("chmod", kPath, kOct, kGen, kGen, kGen, kGen), // 15 + SYSCALL("lchown", kPath, kInt, kInt, kGen, kGen, kGen), // 16 + SYSCALL("break", kHex, kHex, kHex, kHex, kHex, kHex), // 17 + SYSCALL("oldstat", kHex, kHex, kHex, kHex, kHex, kHex), // 18 + SYSCALL("lseek", kGen, kGen, kGen, kGen, kGen, kGen), // 19 + SYSCALL("getpid", kGen, kGen, kGen, kGen, kGen, kGen), // 20 + SYSCALL("mount", kPath, kPath, kString, kHex, kGen, kGen), // 21 + SYSCALL("umount", kHex, kHex, kHex, kHex, kHex, kHex), // 22 + SYSCALL("setuid", kGen, kGen, kGen, kGen, kGen, kGen), // 23 + SYSCALL("getuid", kGen, kGen, kGen, kGen, kGen, kGen), // 24 + SYSCALL("stime", kHex, kHex, kHex, kHex, kHex, kHex), // 25 + SYSCALL("ptrace", kGen, kGen, kGen, kGen, kGen, kGen), // 26 + SYSCALL("alarm", kInt, kGen, kGen, kGen, kGen, kGen), // 27 + SYSCALL("oldfstat", kHex, kHex, kHex, kHex, kHex, kHex), // 28 + SYSCALL("pause", kGen, kGen, kGen, kGen, kGen, kGen), // 29 + SYSCALL("utime", kGen, kGen, kGen, kGen, kGen, kGen), // 30 + SYSCALL("stty", kHex, kHex, kHex, kHex, kHex, kHex), // 31 + SYSCALL("gtty", kHex, kHex, kHex, kHex, kHex, kHex), // 32 + SYSCALL("access", kPath, kHex, kGen, kGen, kGen, kGen), // 33 + SYSCALL("nice", kHex, kHex, kHex, kHex, kHex, kHex), // 34 + SYSCALL("ftime", kHex, kHex, kHex, kHex, kHex, kHex), // 35 + SYSCALL("sync", kGen, kGen, kGen, kGen, kGen, kGen), // 36 + SYSCALL("kill", kInt, kSignal, kGen, kGen, kGen, kGen), // 37 + SYSCALL("rename", kPath, kPath, kGen, kGen, kGen, kGen), // 38 + SYSCALL("mkdir", kPath, kOct, kGen, kGen, kGen, kGen), // 39 + SYSCALL("rmdir", kPath, kGen, kGen, kGen, kGen, kGen), // 40 + SYSCALL("dup", kGen, kGen, kGen, kGen, kGen, kGen), // 41 + SYSCALL("pipe", kGen, kGen, kGen, kGen, kGen, kGen), // 42 + SYSCALL("times", kGen, kGen, kGen, kGen, kGen, kGen), // 43 + SYSCALL("prof", kHex, kHex, kHex, kHex, kHex, kHex), // 44 + SYSCALL("brk", kHex, kGen, kGen, kGen, kGen, kGen), // 45 + SYSCALL("setgid", kGen, kGen, kGen, kGen, kGen, kGen), // 46 + SYSCALL("getgid", kGen, kGen, kGen, kGen, kGen, kGen), // 47 + SYSCALL("signal", kHex, kHex, kHex, kHex, kHex, kHex), // 48 + SYSCALL("geteuid", kGen, kGen, kGen, kGen, kGen, kGen), // 49 + SYSCALL("getegid", kGen, kGen, kGen, kGen, kGen, kGen), // 50 + SYSCALL("acct", kPath, kGen, kGen, kGen, kGen, kGen), // 51 + SYSCALL("umount2", kPath, kHex, kGen, kGen, kGen, kGen), // 52 + SYSCALL("lock", kHex, kHex, kHex, kHex, kHex, kHex), // 53 + SYSCALL("ioctl", kGen, kGen, kGen, kGen, kGen, kGen), // 54 + SYSCALL("fcntl", kGen, kGen, kGen, kGen, kGen, kGen), // 55 + SYSCALL("mpx", kHex, kHex, kHex, kHex, kHex, kHex), // 56 + SYSCALL("setpgid", kGen, kGen, kGen, kGen, kGen, kGen), // 57 + SYSCALL("ulimit", kHex, kHex, kHex, kHex, kHex, kHex), // 58 + SYSCALL("oldolduname", kHex, kHex, kHex, kHex, kHex, kHex), // 59 + SYSCALL("umask", kHex, kGen, kGen, kGen, kGen, kGen), // 60 + SYSCALL("chroot", kPath, kGen, kGen, kGen, kGen, kGen), // 61 + SYSCALL("ustat", kGen, kGen, kGen, kGen, kGen, kGen), // 62 + SYSCALL("dup2", kGen, kGen, kGen, kGen, kGen, kGen), // 63 + SYSCALL("getppid", kGen, kGen, kGen, kGen, kGen, kGen), // 64 + SYSCALL("getpgrp", kGen, kGen, kGen, kGen, kGen, kGen), // 65 + SYSCALL("setsid", kGen, kGen, kGen, kGen, kGen, kGen), // 66 + SYSCALL("sigaction", kHex, kHex, kHex, kHex, kHex, kHex), // 67 + SYSCALL("sgetmask", kHex, kHex, kHex, kHex, kHex, kHex), // 68 + SYSCALL("ssetmask", kHex, kHex, kHex, kHex, kHex, kHex), // 69 + SYSCALL("setreuid", kGen, kGen, kGen, kGen, kGen, kGen), // 70 + SYSCALL("setregid", kGen, kGen, kGen, kGen, kGen, kGen), // 71 + SYSCALL("sigsuspend", kHex, kHex, kHex, kHex, kHex, kHex), // 72 + SYSCALL("sigpending", kHex, kHex, kHex, kHex, kHex, kHex), // 73 + SYSCALL("sethostname", kGen, kGen, kGen, kGen, kGen, kGen), // 74 + SYSCALL("setrlimit", kGen, kGen, kGen, kGen, kGen, kGen), // 75 + SYSCALL("getrlimit", kGen, kGen, kGen, kGen, kGen, kGen), // 76 + SYSCALL("getrusage", kGen, kGen, kGen, kGen, kGen, kGen), // 77 + SYSCALL("gettimeofday", kHex, kHex, kGen, kGen, kGen, kGen), // 78 + SYSCALL("settimeofday", kHex, kHex, kGen, kGen, kGen, kGen), // 79 + SYSCALL("getgroups", kGen, kGen, kGen, kGen, kGen, kGen), // 80 + SYSCALL("setgroups", kGen, kGen, kGen, kGen, kGen, kGen), // 81 + SYSCALL("select", kGen, kGen, kGen, kGen, kGen, kGen), // 82 + SYSCALL("symlink", kPath, kPath, kGen, kGen, kGen, kGen), // 83 + SYSCALL("oldlstat", kHex, kHex, kHex, kHex, kHex, kHex), // 84 + SYSCALL("readlink", kPath, kGen, kInt, kGen, kGen, kGen), // 85 + SYSCALL("uselib", kPath, kGen, kGen, kGen, kGen, kGen), // 86 + SYSCALL("swapon", kPath, kHex, kGen, kGen, kGen, kGen), // 87 + SYSCALL("reboot", kGen, kGen, kGen, kGen, kGen, kGen), // 88 + SYSCALL("readdir", kHex, kHex, kHex, kHex, kHex, kHex), // 89 + SYSCALL("mmap", kHex, kInt, kHex, kHex, kInt, kInt), // 90 + SYSCALL("munmap", kHex, kHex, kGen, kGen, kGen, kGen), // 91 + SYSCALL("truncate", kPath, kInt, kGen, kGen, kGen, kGen), // 92 + SYSCALL("ftruncate", kGen, kGen, kGen, kGen, kGen, kGen), // 93 + SYSCALL("fchmod", kGen, kGen, kGen, kGen, kGen, kGen), // 94 + SYSCALL("fchown", kGen, kGen, kGen, kGen, kGen, kGen), // 95 + SYSCALL("getpriority", kGen, kGen, kGen, kGen, kGen, kGen), // 96 + SYSCALL("setpriority", kGen, kGen, kGen, kGen, kGen, kGen), // 97 + SYSCALL("profil", kHex, kHex, kHex, kHex, kHex, kHex), // 98 + SYSCALL("statfs", kPath, kGen, kGen, kGen, kGen, kGen), // 99 + SYSCALL("fstatfs", kGen, kGen, kGen, kGen, kGen, kGen), // 100 + SYSCALL("ioperm", kGen, kGen, kGen, kGen, kGen, kGen), // 101 + SYSCALL("socketcall", kHex, kHex, kHex, kHex, kHex, kHex), // 102 + SYSCALL("syslog", kGen, kGen, kGen, kGen, kGen, kGen), // 103 + SYSCALL("setitimer", kGen, kGen, kGen, kGen, kGen, kGen), // 104 + SYSCALL("getitimer", kGen, kGen, kGen, kGen, kGen, kGen), // 105 + SYSCALL("stat", kPath, kGen, kGen, kGen, kGen, kGen), // 106 + SYSCALL("lstat", kPath, kGen, kGen, kGen, kGen, kGen), // 107 + SYSCALL("fstat", kInt, kHex, kGen, kGen, kGen, kGen), // 108 + SYSCALL("olduname", kHex, kHex, kHex, kHex, kHex, kHex), // 109 + SYSCALL("iopl", kGen, kGen, kGen, kGen, kGen, kGen), // 110 + SYSCALL("vhangup", kGen, kGen, kGen, kGen, kGen, kGen), // 111 + SYSCALL("idle", kHex, kHex, kHex, kHex, kHex, kHex), // 112 + SYSCALL("vm86", kHex, kHex, kHex, kHex, kHex, kHex), // 113 + SYSCALL("wait4", kInt, kHex, kHex, kHex, kGen, kGen), // 114 + SYSCALL("swapoff", kPath, kGen, kGen, kGen, kGen, kGen), // 115 + SYSCALL("sysinfo", kGen, kGen, kGen, kGen, kGen, kGen), // 116 + SYSCALL("ipc", kHex, kHex, kHex, kHex, kHex, kHex), // 117 + SYSCALL("fsync", kGen, kGen, kGen, kGen, kGen, kGen), // 118 + SYSCALL("sigreturn", kHex, kHex, kHex, kHex, kHex, kHex), // 119 + SYSCALL("clone", kCloneFlag, kHex, kHex, kHex, kHex, kGen), // 120 + SYSCALL("setdomainname", kGen, kGen, kGen, kGen, kGen, kGen), // 121 + SYSCALL("uname", kGen, kGen, kGen, kGen, kGen, kGen), // 122 + SYSCALL("modify_ldt", kGen, kGen, kGen, kGen, kGen, kGen), // 123 + SYSCALL("adjtimex", kGen, kGen, kGen, kGen, kGen, kGen), // 124 + SYSCALL("mprotect", kHex, kHex, kHex, kGen, kGen, kGen), // 125 + SYSCALL("sigprocmask", kHex, kHex, kHex, kHex, kHex, kHex), // 126 + SYSCALL("create_module", kGen, kGen, kGen, kGen, kGen, kGen), // 127 + SYSCALL("init_module", kGen, kGen, kGen, kGen, kGen, kGen), // 128 + SYSCALL("delete_module", kGen, kGen, kGen, kGen, kGen, kGen), // 129 + SYSCALL("get_kernel_syms", kGen, kGen, kGen, kGen, kGen, kGen), // 130 + SYSCALL("quotactl", kInt, kPath, kInt, kGen, kGen, kGen), // 131 + SYSCALL("getpgid", kGen, kGen, kGen, kGen, kGen, kGen), // 132 + SYSCALL("fchdir", kGen, kGen, kGen, kGen, kGen, kGen), // 133 + SYSCALL("bdflush", kHex, kHex, kHex, kHex, kHex, kHex), // 134 + SYSCALL("sysfs", kGen, kGen, kGen, kGen, kGen, kGen), // 135 + SYSCALL("personality", kGen, kGen, kGen, kGen, kGen, kGen), // 136 + SYSCALL("afs_syscall", kGen, kGen, kGen, kGen, kGen, kGen), // 137 + SYSCALL("setfsuid", kGen, kGen, kGen, kGen, kGen, kGen), // 138 + SYSCALL("setfsgid", kGen, kGen, kGen, kGen, kGen, kGen), // 139 + SYSCALL("_llseek", kHex, kHex, kHex, kHex, kHex, kHex), // 140 + SYSCALL("getdents", kGen, kGen, kGen, kGen, kGen, kGen), // 141 + SYSCALL("_newselect", kHex, kHex, kHex, kHex, kHex, kHex), // 142 + SYSCALL("flock", kGen, kGen, kGen, kGen, kGen, kGen), // 143 + SYSCALL("msync", kGen, kGen, kGen, kGen, kGen, kGen), // 144 + SYSCALL("readv", kGen, kGen, kGen, kGen, kGen, kGen), // 145 + SYSCALL("writev", kGen, kGen, kGen, kGen, kGen, kGen), // 146 + SYSCALL("getsid", kGen, kGen, kGen, kGen, kGen, kGen), // 147 + SYSCALL("fdatasync", kGen, kGen, kGen, kGen, kGen, kGen), // 148 + SYSCALL("_sysctl", kGen, kGen, kGen, kGen, kGen, kGen), // 149 + SYSCALL("mlock", kGen, kGen, kGen, kGen, kGen, kGen), // 150 + SYSCALL("munlock", kGen, kGen, kGen, kGen, kGen, kGen), // 151 + SYSCALL("mlockall", kGen, kGen, kGen, kGen, kGen, kGen), // 152 + SYSCALL("munlockall", kGen, kGen, kGen, kGen, kGen, kGen), // 153 + SYSCALL("sched_setparam", kGen, kGen, kGen, kGen, kGen, kGen), // 154 + SYSCALL("sched_getparam", kGen, kGen, kGen, kGen, kGen, kGen), // 155 + SYSCALL("sched_setscheduler", kGen, kGen, kGen, kGen, kGen, kGen), // 156 + SYSCALL("sched_getscheduler", kGen, kGen, kGen, kGen, kGen, kGen), // 157 + SYSCALL("sched_yield", kGen, kGen, kGen, kGen, kGen, kGen), // 158 + SYSCALL("sched_get_priority_max", kGen, kGen, kGen, kGen, kGen, + kGen), // 159 + SYSCALL("sched_get_priority_min", kGen, kGen, kGen, kGen, kGen, + kGen), // 160 + SYSCALL("sched_rr_get_interval", kGen, kGen, kGen, kGen, kGen, + kGen), // 161 + SYSCALL("nanosleep", kHex, kHex, kGen, kGen, kGen, kGen), // 162 + SYSCALL("mremap", kGen, kGen, kGen, kGen, kGen, kGen), // 163 + SYSCALL("setresuid", kGen, kGen, kGen, kGen, kGen, kGen), // 164 + SYSCALL("getresuid", kGen, kGen, kGen, kGen, kGen, kGen), // 165 + SYSCALL("query_module", kGen, kGen, kGen, kGen, kGen, kGen), // 166 + SYSCALL("poll", kGen, kGen, kGen, kGen, kGen, kGen), // 167 + SYSCALL("nfsservctl", kGen, kGen, kGen, kGen, kGen, kGen), // 168 + SYSCALL("setresgid", kGen, kGen, kGen, kGen, kGen, kGen), // 169 + SYSCALL("getresgid", kGen, kGen, kGen, kGen, kGen, kGen), // 170 + SYSCALL("prctl", kInt, kHex, kHex, kHex, kHex, kGen), // 171 + SYSCALL("rt_sigreturn", kGen, kGen, kGen, kGen, kGen, kGen), // 172 + SYSCALL("rt_sigaction", kSignal, kHex, kHex, kInt, kGen, kGen), // 173 + SYSCALL("rt_sigprocmask", kGen, kGen, kGen, kGen, kGen, kGen), // 174 + SYSCALL("rt_sigpending", kGen, kGen, kGen, kGen, kGen, kGen), // 175 + SYSCALL("rt_sigtimedwait", kGen, kGen, kGen, kGen, kGen, kGen), // 176 + SYSCALL("rt_sigqueueinfo", kGen, kGen, kGen, kGen, kGen, kGen), // 177 + SYSCALL("rt_sigsuspend", kGen, kGen, kGen, kGen, kGen, kGen), // 178 + SYSCALL("pread64", kGen, kGen, kGen, kGen, kGen, kGen), // 179 + SYSCALL("pwrite64", kGen, kGen, kGen, kGen, kGen, kGen), // 180 + SYSCALL("chown", kPath, kInt, kInt, kGen, kGen, kGen), // 181 + SYSCALL("getcwd", kGen, kGen, kGen, kGen, kGen, kGen), // 182 + SYSCALL("capget", kGen, kGen, kGen, kGen, kGen, kGen), // 183 + SYSCALL("capset", kGen, kGen, kGen, kGen, kGen, kGen), // 184 + SYSCALL("sigaltstack", kGen, kGen, kGen, kGen, kGen, kGen), // 185 + SYSCALL("sendfile", kGen, kGen, kGen, kGen, kGen, kGen), // 186 + SYSCALL("getpmsg", kGen, kGen, kGen, kGen, kGen, kGen), // 187 + SYSCALL("putpmsg", kGen, kGen, kGen, kGen, kGen, kGen), // 188 + SYSCALL("vfork", kGen, kGen, kGen, kGen, kGen, kGen), // 189 + SYSCALL("ugetrlimit", kHex, kHex, kHex, kHex, kHex, kHex), // 190 + SYSCALL("readahead", kGen, kGen, kGen, kGen, kGen, kGen), // 191 + SYSCALL("mmap2", kHex, kHex, kHex, kHex, kHex, kHex), // 192 + SYSCALL("truncate64", kHex, kHex, kHex, kHex, kHex, kHex), // 193 + SYSCALL("ftruncate64", kHex, kHex, kHex, kHex, kHex, kHex), // 194 + SYSCALL("stat64", kHex, kHex, kHex, kHex, kHex, kHex), // 195 + SYSCALL("lstat64", kHex, kHex, kHex, kHex, kHex, kHex), // 196 + SYSCALL("fstat64", kHex, kHex, kHex, kHex, kHex, kHex), // 197 + SYSCALL("pciconfig_read", kHex, kHex, kHex, kHex, kHex, kHex), // 198 + SYSCALL("pciconfig_write", kHex, kHex, kHex, kHex, kHex, kHex), // 199 + SYSCALL("pciconfig_iobase", kHex, kHex, kHex, kHex, kHex, kHex), // 200 + SYSCALL("multiplexer", kHex, kHex, kHex, kHex, kHex, kHex), // 201 + SYSCALL("getdents64", kGen, kGen, kGen, kGen, kGen, kGen), // 202 + SYSCALL("pivot_root", kPath, kPath, kGen, kGen, kGen, kGen), // 203 + SYSCALL("fcntl64", kHex, kHex, kHex, kHex, kHex, kHex), // 204 + SYSCALL("madvise", kGen, kGen, kGen, kGen, kGen, kGen), // 205 + SYSCALL("mincore", kGen, kGen, kGen, kGen, kGen, kGen), // 206 + SYSCALL("gettid", kGen, kGen, kGen, kGen, kGen, kGen), // 207 + SYSCALL("tkill", kInt, kSignal, kGen, kGen, kGen, kGen), // 208 + SYSCALL("setxattr", kPath, kString, kGen, kInt, kHex, kGen), // 209 + SYSCALL("lsetxattr", kPath, kString, kGen, kInt, kHex, kGen), // 210 + SYSCALL("fsetxattr", kGen, kGen, kGen, kGen, kGen, kGen), // 211 + SYSCALL("getxattr", kPath, kString, kGen, kInt, kGen, kGen), // 212 + SYSCALL("lgetxattr", kPath, kString, kGen, kInt, kGen, kGen), // 213 + SYSCALL("fgetxattr", kGen, kGen, kGen, kGen, kGen, kGen), // 214 + SYSCALL("listxattr", kPath, kGen, kInt, kGen, kGen, kGen), // 215 + SYSCALL("llistxattr", kPath, kGen, kInt, kGen, kGen, kGen), // 216 + SYSCALL("flistxattr", kGen, kGen, kGen, kGen, kGen, kGen), // 217 + SYSCALL("removexattr", kPath, kString, kGen, kGen, kGen, kGen), // 218 + SYSCALL("lremovexattr", kGen, kGen, kGen, kGen, kGen, kGen), // 219 + SYSCALL("fremovexattr", kGen, kGen, kGen, kGen, kGen, kGen), // 220 + SYSCALL("futex", kGen, kGen, kGen, kGen, kGen, kGen), // 221 + SYSCALL("sched_setaffinity", kGen, kGen, kGen, kGen, kGen, kGen), // 222 + SYSCALL("sched_getaffinity", kGen, kGen, kGen, kGen, kGen, kGen), // 223 + SYSCALLS_UNUSED("UNUSED224"), // 224 + SYSCALL("tuxcall", kGen, kGen, kGen, kGen, kGen, kGen), // 225 + SYSCALL("sendfile64", kHex, kHex, kHex, kHex, kHex, kHex), // 226 + SYSCALL("io_setup", kGen, kGen, kGen, kGen, kGen, kGen), // 227 + SYSCALL("io_destroy", kGen, kGen, kGen, kGen, kGen, kGen), // 228 + SYSCALL("io_getevents", kGen, kGen, kGen, kGen, kGen, kGen), // 229 + SYSCALL("io_submit", kGen, kGen, kGen, kGen, kGen, kGen), // 230 + SYSCALL("io_cancel", kGen, kGen, kGen, kGen, kGen, kGen), // 231 + SYSCALL("set_tid_address", kHex, kGen, kGen, kGen, kGen, kGen), // 232 + SYSCALL("fadvise64", kGen, kGen, kGen, kGen, kGen, kGen), // 233 + SYSCALL("exit_group", kInt, kGen, kGen, kGen, kGen, kGen), // 234 + SYSCALL("lookup_dcookie", kGen, kGen, kGen, kGen, kGen, kGen), // 235 + SYSCALL("epoll_create", kGen, kGen, kGen, kGen, kGen, kGen), // 236 + SYSCALL("epoll_ctl", kGen, kGen, kGen, kGen, kGen, kGen), // 237 + SYSCALL("epoll_wait", kGen, kGen, kGen, kGen, kGen, kGen), // 238 + SYSCALL("remap_file_pages", kGen, kGen, kGen, kGen, kGen, kGen), // 239 + SYSCALL("timer_create", kGen, kGen, kGen, kGen, kGen, kGen), // 240 + SYSCALL("timer_settime", kGen, kGen, kGen, kGen, kGen, kGen), // 241 + SYSCALL("timer_gettime", kGen, kGen, kGen, kGen, kGen, kGen), // 242 + SYSCALL("timer_getoverrun", kGen, kGen, kGen, kGen, kGen, kGen), // 243 + SYSCALL("timer_delete", kGen, kGen, kGen, kGen, kGen, kGen), // 244 + SYSCALL("clock_settime", kGen, kGen, kGen, kGen, kGen, kGen), // 245 + SYSCALL("clock_gettime", kGen, kGen, kGen, kGen, kGen, kGen), // 246 + SYSCALL("clock_getres", kGen, kGen, kGen, kGen, kGen, kGen), // 247 + SYSCALL("clock_nanosleep", kGen, kGen, kGen, kGen, kGen, kGen), // 248 + SYSCALL("swapcontext", kHex, kHex, kHex, kHex, kHex, kHex), // 249 + SYSCALL("tgkill", kInt, kInt, kSignal, kGen, kGen, kGen), // 250 + SYSCALL("utimes", kGen, kGen, kGen, kGen, kGen, kGen), // 251 + SYSCALL("statfs64", kHex, kHex, kHex, kHex, kHex, kHex), // 252 + SYSCALL("fstatfs64", kHex, kHex, kHex, kHex, kHex, kHex), // 253 + SYSCALL("fadvise64_64", kHex, kHex, kHex, kHex, kHex, kHex), // 254 + SYSCALL("rtas", kHex, kHex, kHex, kHex, kHex, kHex), // 255 + SYSCALL("sys_debug_setcontext", kHex, kHex, kHex, kHex, kHex, kHex), // 256 + SYSCALLS_UNUSED("UNUSED257"), // 257 + SYSCALL("migrate_pages", kGen, kGen, kGen, kGen, kGen, kGen), // 258 + SYSCALL("mbind", kGen, kGen, kGen, kGen, kGen, kGen), // 259 + SYSCALL("get_mempolicy", kGen, kGen, kGen, kGen, kGen, kGen), // 260 + SYSCALL("set_mempolicy", kGen, kGen, kGen, kGen, kGen, kGen), // 261 + SYSCALL("mq_open", kGen, kGen, kGen, kGen, kGen, kGen), // 262 + SYSCALL("mq_unlink", kGen, kGen, kGen, kGen, kGen, kGen), // 263 + SYSCALL("mq_timedsend", kGen, kGen, kGen, kGen, kGen, kGen), // 264 + SYSCALL("mq_timedreceive", kGen, kGen, kGen, kGen, kGen, kGen), // 265 + SYSCALL("mq_notify", kGen, kGen, kGen, kGen, kGen, kGen), // 266 + SYSCALL("mq_getsetattr", kGen, kGen, kGen, kGen, kGen, kGen), // 267 + SYSCALL("kexec_load", kGen, kGen, kGen, kGen, kGen, kGen), // 268 + SYSCALL("add_key", kGen, kGen, kGen, kGen, kGen, kGen), // 269 + SYSCALL("request_key", kGen, kGen, kGen, kGen, kGen, kGen), // 270 + SYSCALL("keyctl", kGen, kGen, kGen, kGen, kGen, kGen), // 271 + SYSCALL("waitid", kGen, kGen, kGen, kGen, kGen, kGen), // 272 + SYSCALL("ioprio_set", kGen, kGen, kGen, kGen, kGen, kGen), // 273 + SYSCALL("ioprio_get", kGen, kGen, kGen, kGen, kGen, kGen), // 274 + SYSCALL("inotify_init", kGen, kGen, kGen, kGen, kGen, kGen), // 275 + SYSCALL("inotify_add_watch", kGen, kGen, kGen, kGen, kGen, kGen), // 276 + SYSCALL("inotify_rm_watch", kGen, kGen, kGen, kGen, kGen, kGen), // 277 + SYSCALL("spu_run", kHex, kHex, kHex, kHex, kHex, kHex), // 278 + SYSCALL("spu_create", kHex, kHex, kHex, kHex, kHex, kHex), // 279 + SYSCALL("pselect6", kGen, kGen, kGen, kGen, kGen, kGen), // 280 + SYSCALL("ppoll", kGen, kGen, kGen, kGen, kGen, kGen), // 281 + SYSCALL("unshare", kGen, kGen, kGen, kGen, kGen, kGen), // 282 + SYSCALL("splice", kGen, kGen, kGen, kGen, kGen, kGen), // 283 + SYSCALL("tee", kGen, kGen, kGen, kGen, kGen, kGen), // 284 + SYSCALL("vmsplice", kGen, kGen, kGen, kGen, kGen, kGen), // 285 + SYSCALL("openat", kGen, kPath, kOct, kHex, kGen, kGen), // 286 + SYSCALL("mkdirat", kGen, kPath, kGen, kGen, kGen, kGen), // 287 + SYSCALL("mknodat", kGen, kPath, kGen, kGen, kGen, kGen), // 288 + SYSCALL("fchownat", kGen, kPath, kGen, kGen, kGen, kGen), // 289 + SYSCALL("futimesat", kGen, kPath, kGen, kGen, kGen, kGen), // 290 + SYSCALL("newfstatat", kGen, kPath, kGen, kGen, kGen, kGen), // 291 + SYSCALL("unlinkat", kGen, kPath, kGen, kGen, kGen, kGen), // 292 + SYSCALL("renameat", kGen, kPath, kGen, kPath, kGen, kGen), // 293 + SYSCALL("linkat", kGen, kPath, kGen, kPath, kGen, kGen), // 294 + SYSCALL("symlinkat", kPath, kGen, kPath, kGen, kGen, kGen), // 295 + SYSCALL("readlinkat", kGen, kPath, kGen, kGen, kGen, kGen), // 296 + SYSCALL("fchmodat", kGen, kPath, kGen, kGen, kGen, kGen), // 297 + SYSCALL("faccessat", kGen, kPath, kGen, kGen, kGen, kGen), // 298 + SYSCALL("get_robust_list", kGen, kGen, kGen, kGen, kGen, kGen), // 299 + SYSCALL("set_robust_list", kGen, kGen, kGen, kGen, kGen, kGen), // 300 + SYSCALL("move_pages", kGen, kGen, kGen, kGen, kGen, kGen), // 301 + SYSCALL("getcpu", kHex, kHex, kHex, kGen, kGen, kGen), // 302 + SYSCALL("epoll_pwait", kGen, kGen, kGen, kGen, kGen, kGen), // 303 + SYSCALL("utimensat", kGen, kGen, kGen, kGen, kGen, kGen), // 304 + SYSCALL("signalfd", kGen, kGen, kGen, kGen, kGen, kGen), // 305 + SYSCALL("timerfd_create", kGen, kGen, kGen, kGen, kGen, kGen), // 306 + SYSCALL("eventfd", kGen, kGen, kGen, kGen, kGen, kGen), // 307 + SYSCALL("sync_file_range2", kHex, kHex, kHex, kHex, kHex, kHex), // 308 + SYSCALL("fallocate", kGen, kGen, kGen, kGen, kGen, kGen), // 309 + SYSCALL("subpage_prot", kHex, kHex, kHex, kHex, kHex, kHex), // 310 + SYSCALL("timerfd_settime", kGen, kGen, kGen, kGen, kGen, kGen), // 311 + SYSCALL("timerfd_gettime", kGen, kGen, kGen, kGen, kGen, kGen), // 312 + SYSCALL("signalfd4", kGen, kGen, kGen, kGen, kGen, kGen), // 313 + SYSCALL("eventfd2", kGen, kGen, kGen, kGen, kGen, kGen), // 314 + SYSCALL("epoll_create1", kGen, kGen, kGen, kGen, kGen, kGen), // 315 + SYSCALL("dup3", kGen, kGen, kGen, kGen, kGen, kGen), // 316 + SYSCALL("pipe2", kGen, kGen, kGen, kGen, kGen, kGen), // 317 + SYSCALL("inotify_init1", kGen, kGen, kGen, kGen, kGen, kGen), // 318 + SYSCALL("perf_event_open", kGen, kGen, kGen, kGen, kGen, kGen), // 319 + SYSCALL("preadv", kGen, kGen, kGen, kGen, kGen, kGen), // 320 + SYSCALL("pwritev", kGen, kGen, kGen, kGen, kGen, kGen), // 321 + SYSCALL("rt_tgsigqueueinfo", kGen, kGen, kGen, kGen, kGen, kGen), // 322 + SYSCALL("fanotify_init", kHex, kHex, kInt, kGen, kGen, kGen), // 323 + SYSCALL("fanotify_mark", kInt, kHex, kInt, kPath, kGen, kGen), // 324 + SYSCALL("prlimit64", kInt, kInt, kHex, kHex, kGen, kGen), // 325 + SYSCALL("socket", kAddressFamily, kInt, kInt, kGen, kGen, kGen), // 326 + SYSCALL("bind", kGen, kGen, kGen, kGen, kGen, kGen), // 327 + SYSCALL("connect", kInt, kSockaddr, kInt, kGen, kGen, kGen), // 328 + SYSCALL("listen", kGen, kGen, kGen, kGen, kGen, kGen), // 329 + SYSCALL("accept", kGen, kGen, kGen, kGen, kGen, kGen), // 330 + SYSCALL("getsockname", kGen, kGen, kGen, kGen, kGen, kGen), // 331 + SYSCALL("getpeername", kGen, kGen, kGen, kGen, kGen, kGen), // 332 + SYSCALL("socketpair", kGen, kGen, kGen, kGen, kGen, kGen), // 333 + SYSCALL("send", kHex, kHex, kHex, kHex, kHex, kHex), // 334 + SYSCALL("sendto", kInt, kGen, kInt, kHex, kSockaddr, kInt), // 335 + SYSCALL("recv", kHex, kHex, kHex, kHex, kHex, kHex), // 336 + SYSCALL("recvfrom", kGen, kGen, kGen, kGen, kGen, kGen), // 337 + SYSCALL("shutdown", kGen, kGen, kGen, kGen, kGen, kGen), // 338 + SYSCALL("setsockopt", kGen, kGen, kGen, kGen, kGen, kGen), // 339 + SYSCALL("getsockopt", kGen, kGen, kGen, kGen, kGen, kGen), // 340 + SYSCALL("sendmsg", kInt, kSockmsghdr, kHex, kGen, kGen, kGen), // 341 + SYSCALL("recvmsg", kGen, kGen, kGen, kGen, kGen, kGen), // 342 + SYSCALL("recvmmsg", kInt, kHex, kHex, kHex, kGen, kGen), // 343 + SYSCALL("accept4", kGen, kGen, kGen, kGen, kGen, kGen), // 344 + SYSCALL("name_to_handle_at", kInt, kGen, kHex, kHex, kHex, kGen), // 345 + SYSCALL("open_by_handle_at", kInt, kHex, kHex, kGen, kGen, kGen), // 346 + SYSCALL("clock_adjtime", kInt, kHex, kGen, kGen, kGen, kGen), // 347 + SYSCALL("syncfs", kInt, kGen, kGen, kGen, kGen, kGen), // 348 + SYSCALL("sendmmsg", kInt, kHex, kInt, kHex, kGen, kGen), // 349 + SYSCALL("setns", kInt, kHex, kGen, kGen, kGen, kGen), // 350 + SYSCALL("process_vm_readv", kInt, kHex, kInt, kHex, kInt, kInt), // 351 + SYSCALL("process_vm_writev", kInt, kHex, kInt, kHex, kInt, kInt), // 352 + SYSCALL("finit_module", kInt, kPath, kHex, kGen, kGen, kGen), // 353 + SYSCALL("kcmp", kInt, kInt, kInt, kHex, kHex, kGen), // 354 + SYSCALL("sched_setattr", kGen, kGen, kGen, kGen, kGen, kGen), // 355 + SYSCALL("sched_getattr", kGen, kGen, kGen, kGen, kGen, kGen), // 356 + SYSCALL("renameat2", kGen, kPath, kGen, kPath, kGen, kGen), // 357 + SYSCALL("seccomp", kGen, kGen, kGen, kGen, kGen, kGen), // 358 + SYSCALL("getrandom", kGen, kGen, kGen, kGen, kGen, kGen), // 359 + SYSCALL("memfd_create", kGen, kGen, kGen, kGen, kGen, kGen), // 360 + SYSCALL("bpf", kHex, kHex, kHex, kHex, kHex, kHex), // 361 + SYSCALL("execveat", kHex, kHex, kHex, kHex, kHex, kHex), // 362 + SYSCALL("switch_endian", kHex, kHex, kHex, kHex, kHex, kHex), // 363 + SYSCALL("userfaultfd", kHex, kHex, kHex, kHex, kHex, kHex), // 364 + SYSCALL("membarrier", kHex, kHex, kHex, kHex, kHex, kHex), // 365 + SYSCALLS_UNUSED("UNUSED366"), // 366 + SYSCALLS_UNUSED("UNUSED367"), // 367 + SYSCALLS_UNUSED("UNUSED368"), // 368 + SYSCALLS_UNUSED("UNUSED369"), // 369 + SYSCALLS_UNUSED("UNUSED370"), // 370 + SYSCALLS_UNUSED("UNUSED371"), // 371 + SYSCALLS_UNUSED("UNUSED372"), // 372 + SYSCALLS_UNUSED("UNUSED373"), // 373 + SYSCALLS_UNUSED("UNUSED374"), // 374 + SYSCALLS_UNUSED("UNUSED375"), // 375 + SYSCALLS_UNUSED("UNUSED376"), // 376 + SYSCALLS_UNUSED("UNUSED377"), // 377 + SYSCALL("mlock2", kHex, kHex, kHex, kHex, kHex, kHex), // 378 + SYSCALL("copy_file_range", kHex, kHex, kHex, kHex, kHex, kHex), // 379 + SYSCALL("preadv2", kHex, kHex, kHex, kHex, kHex, kHex), // 380 + SYSCALL("pwritev2", kHex, kHex, kHex, kHex, kHex, kHex), // 381 +}; + +#endif + +#undef SYSCALLS_UNUSED00_99 +#undef SYSCALLS_UNUSED50_99 +#undef SYSCALLS_UNUSED00_49 +#undef SYSCALLS_UNUSED0_9 +#undef SYSCALLS_UNUSED + +#undef SYSCALL_HELPER +#undef SYSCALL_NUM_ARGS_HELPER +#undef SYSCALL_NUM_ARGS +#undef SYSCALL +#undef SYSCALL_WITH_UNKNOWN_ARGS + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/syscall_defs.h b/sandboxed_api/sandbox2/syscall_defs.h new file mode 100644 index 0000000..ffcbd26 --- /dev/null +++ b/sandboxed_api/sandbox2/syscall_defs.h @@ -0,0 +1,80 @@ +#ifndef SANDBOXED_API_SANDBOX2_SYSCALL_DEFS_H_ +#define SANDBOXED_API_SANDBOX2_SYSCALL_DEFS_H_ + +#include +#include +#include +#include + +#include "absl/types/span.h" + +namespace sandbox2 { + +namespace syscalls { +constexpr int kMaxArgs = 6; +} + +class SyscallTable { + public: + // Type of a given syscall argument. Used with argument conversion routines. + enum ArgType { + kGen = 1, + kInt, + kPath, + kHex, + kOct, + kSocketCall, + kSocketCallPtr, + kSignal, + kString, + kAddressFamily, + kSockaddr, + kSockmsghdr, + kCloneFlag, + }; + + // Single syscall definition + struct Entry { + const char* const name; + const int num_args; + const ArgType arg_types[syscalls::kMaxArgs]; + + // Returns the number of arguments which given syscall takes. + int GetNumArgs() const { + if (num_args < 0 || num_args > syscalls::kMaxArgs) { + return syscalls::kMaxArgs; + } + return num_args; + } + + std::vector GetArgumentsDescription( + const uint64_t values[syscalls::kMaxArgs], pid_t pid) const; + }; + +#if defined(__x86_64__) + static const absl::Span kSyscallDataX8664; + static const absl::Span kSyscallDataX8632; +#elif defined(__powerpc64__) + static const absl::Span kSyscallDataPPC64; +#endif + + constexpr SyscallTable() = default; + constexpr SyscallTable(absl::Span data) : data_(data) {} + + int size() { return data_.size(); } + const Entry& GetEntry(uint64_t syscall) const { + static Entry invalid_entry{ + nullptr, syscalls::kMaxArgs, {kGen, kGen, kGen, kGen, kGen, kGen}}; + if (syscall < data_.size()) { + return data_[syscall]; + } + return invalid_entry; + } + + private: + const absl::Span data_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_SYSCALL_DEFS_H_ diff --git a/sandboxed_api/sandbox2/syscall_test.cc b/sandboxed_api/sandbox2/syscall_test.cc new file mode 100644 index 0000000..58515b3 --- /dev/null +++ b/sandboxed_api/sandbox2/syscall_test.cc @@ -0,0 +1,62 @@ +// 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/syscall.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" + +using ::testing::Eq; +using ::testing::StartsWith; + +namespace sandbox2 { +namespace { + +TEST(SyscallTest, Basic) { + Syscall::Args args{1, 0xbadbeef, 5}; + Syscall syscall(Syscall::GetHostArch(), __NR_read, args); + + EXPECT_THAT(syscall.pid(), Eq(-1)); + EXPECT_THAT(syscall.arch(), Eq(Syscall::GetHostArch())); + EXPECT_THAT(syscall.nr(), Eq(__NR_read)); + EXPECT_THAT(syscall.args(), Eq(args)); + EXPECT_THAT(syscall.stack_pointer(), Eq(0)); + EXPECT_THAT(syscall.instruction_pointer(), Eq(0)); + + EXPECT_THAT(syscall.GetName(), Eq("read")); + auto arg_desc = syscall.GetArgumentsDescription(); + EXPECT_THAT(arg_desc.size(), Eq(3)); + EXPECT_THAT(arg_desc[0], Eq("0x1 [1]")); + EXPECT_THAT(arg_desc[1], Eq("0xbadbeef")); + EXPECT_THAT(arg_desc[2], Eq("0x5 [5]")); + EXPECT_THAT( + syscall.GetDescription(), + Eq(absl::StrCat(Syscall::GetArchDescription(Syscall::GetHostArch()), + " read [", __NR_read, + "](0x1 [1], 0xbadbeef, 0x5 [5]) IP: 0, STACK: 0"))); +} + +TEST(SyscallTest, Empty) { + Syscall syscall; + + EXPECT_THAT(syscall.arch(), Eq(Syscall::kUnknown)); + EXPECT_THAT(syscall.GetName(), StartsWith("UNKNOWN")); + EXPECT_THAT(syscall.GetArgumentsDescription().size(), Eq(Syscall::kMaxArgs)); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/testcases/BUILD.bazel b/sandboxed_api/sandbox2/testcases/BUILD.bazel new file mode 100644 index 0000000..2fc0e81 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/BUILD.bazel @@ -0,0 +1,269 @@ +# 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: test cases for sandbox2 unit tests. +# +# The following cc_binary options avoid dynamic linking which uses a lot of +# syscalls (open, mmap, etc.): +# linkopts = ["-static"] +# linkstatic = 1 +# features = ["-pie"] +# Bazel adds -pie by default but -static is incompatible with it, so we use +# the features flag to force it off. + +package(default_visibility = [ + "//sandboxed_api/sandbox2:__subpackages__", +]) + +licenses(["notice"]) # Apache 2.0 + +STATIC_LINKOPTS = [ + # Necessary for linking pthread statically into the binary. See the + # answer to https://stackoverflow.com/questions/35116327/ for context. + # The odd '-Wl,' prefix before '-lpthread' is a workaround for Bazel's + # behavior when constructing the final linker command line. + "-Wl,--whole-archive", + "-Wl,-lpthread", + "-Wl,--no-whole-archive", +] + +cc_binary( + name = "abort", + testonly = 1, + srcs = ["abort.cc"], + deps = ["//sandboxed_api/util:raw_logging"], +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "add_policy_on_syscalls", + testonly = 1, + srcs = ["add_policy_on_syscalls.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "buffer", + testonly = 1, + srcs = ["buffer.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries + deps = [ + "//sandboxed_api/sandbox2:buffer", + "//sandboxed_api/sandbox2:comms", + "@com_google_absl//absl/strings:str_format", + ], +) + +cc_binary( + name = "ipc", + testonly = 1, + srcs = ["ipc.cc"], + deps = [ + "//sandboxed_api/sandbox2:client", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/util:raw_logging", + "@com_google_absl//absl/strings", + ], +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "malloc_system", + testonly = 1, + srcs = ["malloc.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +cc_binary( + name = "minimal_dynamic", + testonly = 1, + srcs = ["minimal.cc"], +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "minimal", + testonly = 1, + srcs = ["minimal.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "personality", + testonly = 1, + srcs = ["personality.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "pidcomms", + testonly = 1, + srcs = ["pidcomms.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries + deps = [ + "//sandboxed_api/sandbox2:client", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/util:raw_logging", + ], +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "policy", + testonly = 1, + srcs = ["policy.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "print_fds", + testonly = 1, + srcs = ["print_fds.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "sanitizer", + testonly = 1, + srcs = ["sanitizer.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "sleep", + testonly = 1, + srcs = ["sleep.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +# security: disable=cc-static-no-pie +cc_binary( + name = "symbolize", + testonly = 1, + srcs = ["symbolize.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries + deps = [ + "//sandboxed_api/sandbox2/util:temp_file", + "//sandboxed_api/util:raw_logging", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + ], +) + +cc_binary( + name = "tsync", + testonly = 1, + srcs = ["tsync.cc"], + deps = [ + "//sandboxed_api/sandbox2:client", + "//sandboxed_api/sandbox2:comms", + ], +) + +cc_binary( + name = "hostname", + testonly = 1, + srcs = ["hostname.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +cc_binary( + name = "limits", + testonly = 1, + srcs = ["limits.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) + +cc_binary( + name = "namespace", + testonly = 1, + srcs = ["namespace.cc"], + features = [ + "-pie", + "fully_static_link", # link libc statically + ], + linkopts = STATIC_LINKOPTS, + linkstatic = 1, # prefer static libraries +) diff --git a/sandboxed_api/sandbox2/testcases/abort.cc b/sandboxed_api/sandbox2/testcases/abort.cc new file mode 100644 index 0000000..2f86837 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/abort.cc @@ -0,0 +1,26 @@ +// 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 binary that immediately raises SIGABRT. +// It is used to check raising of a signal and its handling. + +#include + +#include "sandboxed_api/util/raw_logging.h" + +int main(int argc, char** argv) { + SAPI_RAW_LOG(ERROR, "Raising SIGABRT"); + abort(); + return EXIT_SUCCESS; // Not reached +} diff --git a/sandboxed_api/sandbox2/testcases/add_policy_on_syscalls.cc b/sandboxed_api/sandbox2/testcases/add_policy_on_syscalls.cc new file mode 100644 index 0000000..707d596 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/add_policy_on_syscalls.cc @@ -0,0 +1,39 @@ +// 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 binary that tests a lot of syscalls, to test the AddPolicyOnSyscall +// functionality. + +#include +#include + +#include + +int main() { + unsigned int r, e, s; + char buf[1]; + // 1000 is the UID/GID we use inside the namespaces. + if (getuid() != 1000) return 1; + if (getgid() != 1000) return 2; + if (geteuid() != 1000) return 3; + if (getegid() != 1000) return 4; + if (getresuid(&r, &e, &s) != -1 || errno != 42) return 5; + if (getresgid(&r, &e, &s) != -1 || errno != 42) return 6; + if (read(0, buf, 1) != -1 || errno != 43) return 7; + if (write(1, buf, 1) != -1 || errno != 43) return 8; + + // Trigger a violation. + umask(0); + return 0; +} diff --git a/sandboxed_api/sandbox2/testcases/buffer.cc b/sandboxed_api/sandbox2/testcases/buffer.cc new file mode 100644 index 0000000..823f6cf --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/buffer.cc @@ -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. + +// A binary that uses a buffer from its executor. + +#include +#include + +#include "absl/strings/str_format.h" +#include "sandboxed_api/sandbox2/buffer.h" +#include "sandboxed_api/sandbox2/comms.h" + +int main(int argc, char** argv) { + if (argc != 2) { + absl::FPrintF(stderr, "argc != 2\n"); + return EXIT_FAILURE; + } + + int testno = atoi(argv[1]); // NOLINT + switch (testno) { + case 1: // Dup and map to static FD + { + auto buffer_or = sandbox2::Buffer::CreateFromFd(3); + if (!buffer_or) { + return EXIT_FAILURE; + } + auto buffer = std::move(buffer_or).ValueOrDie(); + uint8_t* buf = buffer->data(); + // Test that we can read data from the executor. + if (buf[0] != 'A') { + return EXIT_FAILURE; + } + // Test that we can write data to the executor. + buf[buffer->size() - 1] = 'B'; + return EXIT_SUCCESS; + } + + case 2: // Send and receive FD + { + sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD); + int fd; + if (!comms.RecvFD(&fd)) { + return EXIT_FAILURE; + } + auto buffer_or = sandbox2::Buffer::CreateFromFd(fd); + if (!buffer_or) { + return EXIT_FAILURE; + } + auto buffer = std::move(buffer_or).ValueOrDie(); + uint8_t* buf = buffer->data(); + // Test that we can read data from the executor. + if (buf[0] != 'A') { + return EXIT_FAILURE; + } + // Test that we can write data to the executor. + buf[buffer->size() - 1] = 'B'; + return EXIT_SUCCESS; + } + + default: + absl::FPrintF(stderr, "Unknown test: %d\n", testno); + return EXIT_FAILURE; + } +} diff --git a/sandboxed_api/sandbox2/testcases/hostname.cc b/sandboxed_api/sandbox2/testcases/hostname.cc new file mode 100644 index 0000000..360d6be --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/hostname.cc @@ -0,0 +1,44 @@ +// 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 binary to test network namespace hostname. +// Usage: ./hostname +// Success only if the hostname is as expected. + +#include + +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 2) { + printf("argc < 2\n"); + return EXIT_FAILURE; + } + + char hostname[1024]; + if (gethostname(hostname, sizeof(hostname)) == -1) { + printf("gethostname: error %d\n", errno); + return EXIT_FAILURE; + } + + if (strcmp(hostname, argv[1]) != 0) { + printf("gethostname: got %s, want %s\n", hostname, argv[1]); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/testcases/ipc.cc b/sandboxed_api/sandbox2/testcases/ipc.cc new file mode 100644 index 0000000..4b5afea --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/ipc.cc @@ -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. + +// A binary that uses comms and client, to receive FDs by name, communicate +// with them, sandboxed or not. + +#include +#include +#include + +#include "absl/strings/numbers.h" +#include "sandboxed_api/sandbox2/client.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/util/raw_logging.h" + +int main(int argc, char** argv) { + if (argc < 2) { + printf("argc < 2\n"); + return EXIT_FAILURE; + } + + sandbox2::Comms sb_comms(sandbox2::Comms::kSandbox2ClientCommsFD); + sandbox2::Client client(&sb_comms); + + int testno; + SAPI_RAW_CHECK(absl::SimpleAtoi(argv[1], &testno), "testno is not a number"); + switch (testno) { + case 1: + break; + case 2: + client.SandboxMeHere(); + break; + case 3: + // In case 3, we're running without a mapped fd. This is to test that the + // Client object parses the environment variable properly in that case. + return EXIT_SUCCESS; + default: + printf("Unknown test: %d\n", testno); + return EXIT_FAILURE; + } + int expected_fd = -1; + if (argc >= 3) { + SAPI_RAW_CHECK(absl::SimpleAtoi(argv[2], &expected_fd), + "expected_fd is not a number"); + } + + int fd = client.GetMappedFD("ipc_test"); + if (expected_fd >= 0 && fd != expected_fd) { + fprintf(stderr, "error mapped fd not as expected, got: %d, want: %d", fd, + expected_fd); + return EXIT_FAILURE; + } + sandbox2::Comms comms(fd); + std::string hello; + if (!comms.RecvString(&hello)) { + fputs("error on comms.RecvString(&hello)", stderr); + return EXIT_FAILURE; + } + + if (!comms.SendString("world")) { + fputs("error on comms.SendString(\"world\")", stderr); + return EXIT_FAILURE; + } + + printf("OK: All tests went OK\n"); + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/testcases/limits.cc b/sandboxed_api/sandbox2/testcases/limits.cc new file mode 100644 index 0000000..616f9f6 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/limits.cc @@ -0,0 +1,104 @@ +// 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 binary to test sandbox2 limits. +// Per setrlimit(2): exceeding RLIMIT_AS with mmap, brk or mremap do not +// kill but fail with ENOMEM. However if we trigger automatic stack +// expansion, for instance with a large stack allocation with alloca(3), +// and we have no alternate stack, then we are killed with SIGSEGV. + +#include + +#include +#include +#include + +int TestMmapUnderLimit(void) { + // mmap should work + void* ptr = mmap(0, 1ULL << 20 /* 1 MiB */, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (ptr == MAP_FAILED) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +int TestMmapAboveLimit(void) { + // mmap should fail with ENOMEM + void* ptr = mmap(0, 100ULL << 20 /* 100 MiB */, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (ptr != MAP_FAILED || errno != ENOMEM) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +// Tests using alloca are marked noinline because clang in optimized mode tries +// to inline the test function, and then "optimizes" it by moving the alloca +// stack allocation to the beginning of main() and merging it with main()'s +// local variable allocation. This is specially inconvenient for TestAllocaBig* +// functions below, because they make an allocation big enough to kill the +// process, and with inlining they get to kill the process every time. +// +// This workaround makes sure the stack allocation is only done when the test +// function is actually called. + +__attribute__((noinline)) int TestAllocaSmallUnderLimit() { + void* ptr = alloca(1ULL << 20 /* 1 MiB */); + printf("alloca worked (ptr=%p)\n", ptr); + return EXIT_SUCCESS; +} + +__attribute__((noinline)) int TestAllocaBigUnderLimit() { + void* ptr = alloca(8ULL << 20 /* 8 MiB */); + printf("We should have been killed by now (ptr=%p)\n", ptr); + return EXIT_FAILURE; +} + +__attribute__((noinline)) int TestAllocaBigAboveLimit() { + void* ptr = alloca(100ULL << 20 /* 100 MiB */); + printf("We should have been killed by now (ptr=%p)\n", ptr); + return EXIT_FAILURE; +} + +int main(int argc, char** argv) { + // Disable buffering. + setbuf(stdin, nullptr); + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + if (argc < 2) { + printf("argc < 2\n"); + return EXIT_FAILURE; + } + + int testno = atoi(argv[1]); // NOLINT + switch (testno) { + case 1: + return TestMmapUnderLimit(); + case 2: + return TestMmapAboveLimit(); + case 3: + return TestAllocaSmallUnderLimit(); + case 4: + return TestAllocaBigUnderLimit(); + case 5: + return TestAllocaBigAboveLimit(); + default: + printf("Unknown test: %d\n", testno); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/testcases/malloc.cc b/sandboxed_api/sandbox2/testcases/malloc.cc new file mode 100644 index 0000000..5f6c8e9 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/malloc.cc @@ -0,0 +1,70 @@ +// 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 binary doing various malloc calls to check that the malloc policy works as +// expected. + +#include +#include + +void test() { + std::vector ptrs; + + for (size_t n = 1; n <= 0x1000000; n *= 2) { + void* buf = malloc(n); + if (buf == nullptr) { + exit(EXIT_FAILURE); + } + ptrs.push_back(buf); + } + + for (size_t n = 1; n <= 0x1000000; n *= 2) { + void* buf = calloc(5, n); + if (buf == nullptr) { + exit(EXIT_FAILURE); + } + ptrs.push_back(buf); + } + + for (int n = 0; n < ptrs.size(); n++) { + void* buf = realloc(ptrs[n], 100); + if (buf == nullptr) { + exit(EXIT_FAILURE); + } + ptrs[n] = buf; + } + + for (auto ptr : ptrs) { + free(ptr); + } + ptrs.clear(); + + // Apply a bit of memory pressure, to trigger alternate allocator behaviors. + for (size_t n = 0; n < 0x200; n++) { + void* buf = malloc(0x400); + if (buf == nullptr) { + exit(EXIT_FAILURE); + } + ptrs.push_back(buf); + } + + for (auto ptr : ptrs) { + free(ptr); + } +} + +int main() { + test(); + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/testcases/minimal.cc b/sandboxed_api/sandbox2/testcases/minimal.cc new file mode 100644 index 0000000..97f6d76 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/minimal.cc @@ -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. + +// A binary doing nothing and returning 0 or 1. +// It is used to find the minimal syscall policy to allow. + +int main(int argc, char** argv) { + if (argc <= 1) { + return 0; + } + return 1; +} diff --git a/sandboxed_api/sandbox2/testcases/namespace.cc b/sandboxed_api/sandbox2/testcases/namespace.cc new file mode 100644 index 0000000..8742b97 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/namespace.cc @@ -0,0 +1,80 @@ +// 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. + +// Checks various things related to namespaces, depending on the first argument: +// ./binary 0 ... : +// Make sure all provided files exist and are RO, return 0 on OK. +// Returns the index of the first non-existing file + 1 on failure. +// ./binary 1 ... : +// Make sure all provided files exist and are RW, return 0 on OK. +// Returns the index of the first non-existing file + 1 on failure. +// ./binary 2 +// Make sure that we run in a PID namespace (this implies getpid() == 1) +// Returns 0 on OK. +// ./binary 3 +// Make sure getuid()/getgid() returns the provided uid/gid (User namespace). +// Returns 0 on OK. +#include +#include + +#include + +int main(int argc, char* argv[]) { + if (argc < 2) { + return 0; + } + + int mode = atoi(argv[1]); // NOLINT(runtime/deprecated_fn) + + switch (mode) { + case 0: { + // Make sure file exist + for (int i = 2; i < argc; i++) { + if (access(argv[i], R_OK)) { + return i - 1; + } + } + } break; + + case 1: { + for (int i = 2; i < argc; i++) { + if (access(argv[i], W_OK)) { + return i - 1; + } + } + } break; + + case 2: { + if (getpid() != 2) { + return getpid(); + } + } break; + + case 3: { + if (argc != 4) { + return 1; + } + + if (getuid() != atoi(argv[2]) // NOLINT(runtime/deprecated_fn) + || getgid() != atoi(argv[3])) { // NOLINT(runtime/deprecated_fn) + return getuid(); + } + } break; + + default: + return 1; + } + + return 0; +} diff --git a/sandboxed_api/sandbox2/testcases/personality.cc b/sandboxed_api/sandbox2/testcases/personality.cc new file mode 100644 index 0000000..f23a0a5 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/personality.cc @@ -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. + +// A binary that calls the unusual personality syscall with arguments. +// It is to test seccomp trace, notify API and checking of arguments. + +#include +#include + +int main(int argc, char** argv) { + syscall(__NR_personality, 1ULL, 2ULL, 3ULL, 4ULL, 5ULL, 6ULL); + return 22; +} diff --git a/sandboxed_api/sandbox2/testcases/pidcomms.cc b/sandboxed_api/sandbox2/testcases/pidcomms.cc new file mode 100644 index 0000000..d647c8d --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/pidcomms.cc @@ -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. + +// A binary that communicates with comms before being sandboxed. + +#include "sandboxed_api/sandbox2/client.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/util/raw_logging.h" + +int main(int argc, char** argv) { + sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD); + + // Exchange data with sandbox sandbox (parent) before sandboxing is enabled. + SAPI_RAW_CHECK(comms.SendBool(true), "Sending data to the executor"); + + sandbox2::Client client(&comms); + client.SandboxMeHere(); + + return 33; +} diff --git a/sandboxed_api/sandbox2/testcases/policy.cc b/sandboxed_api/sandbox2/testcases/policy.cc new file mode 100644 index 0000000..c41ef16 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/policy.cc @@ -0,0 +1,117 @@ +// 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 binary that tries x86_64 compat syscalls, ptrace and clone untraced. + +#include +#include +#include +#include + +#include +#include +#include + +#if defined(__x86_64__) +void TestAMD64SyscallMismatch() { + int64_t result; + + // exit() is allowed, but not if called via 32-bit syscall. + asm("movq $1, %%rax\n" // __NR_exit: 1 in 32-bit (60 in 64-bit) + "movq $42, %%rbx\n" // int error_code: 42 + "int $0x80\n" + "movq %%rax, %0\n" + : "=r"(result) + : + : "rax", "rbx"); + exit(-result); +} + +void TestAMD64SyscallMismatchFs() { + int64_t result; + char filename[] = "/etc/passwd"; + + // access("/etc/passwd") is allowed, but not if called via 32-bit syscall. + asm("movq $33, %%rax\n" // __NR_access: 33 in 32-bit (21 in 64-bit) + "movq %1, %%rbx\n" // const char* filename: /etc/passwd + "movq $0, %%rcx\n" // int mode: F_OK (0), test for existence + "int $0x80\n" + "movq %%rax, %0\n" + : "=r"(result) + : "g"(filename) + : "rax", "rbx", "rcx"); + exit(-result); +} +#endif // defined(__x86_64__) + +void TestPtrace() { + ptrace(PTRACE_SEIZE, getppid(), 0, 0); + + printf("Syscall violation should have been discovered by now\n"); + exit(EXIT_FAILURE); +} + +void TestCloneUntraced() { + syscall(__NR_clone, static_cast(CLONE_UNTRACED), nullptr, nullptr, + nullptr, static_cast(0)); + + printf("Syscall violation should have been discovered by now\n"); + exit(EXIT_FAILURE); +} + +void TestBpf() { + syscall(__NR_bpf, 0, nullptr, 0); + + printf("Syscall violation should have been discovered by now\n"); + exit(EXIT_FAILURE); +} + +int main(int argc, char** argv) { + // Disable buffering. + setbuf(stdin, nullptr); + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + if (argc < 2) { + printf("argc < 3\n"); + return EXIT_FAILURE; + } + + int testno = atoi(argv[1]); // NOLINT + switch (testno) { +#if defined(__x86_64__) + case 1: + TestAMD64SyscallMismatch(); + break; + case 2: + TestAMD64SyscallMismatchFs(); + break; +#endif // defined(__x86_64__) + case 3: + TestPtrace(); + break; + case 4: + TestCloneUntraced(); + break; + case 5: + TestBpf(); + break; + default: + printf("Unknown test: %d\n", testno); + return EXIT_FAILURE; + } + + printf("OK: All tests went OK\n"); + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/testcases/print_fds.cc b/sandboxed_api/sandbox2/testcases/print_fds.cc new file mode 100644 index 0000000..877879c --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/print_fds.cc @@ -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. + +// A binary that prints its opened file descriptors. + +#include + +#include + +int main(int argc, char** argv) { + for (int fd = 0; fd < 4096; fd++) { + if (fcntl(fd, F_GETFD) != -1) { + printf("%d\n", fd); + } + } +} diff --git a/sandboxed_api/sandbox2/testcases/sanitizer.cc b/sandboxed_api/sandbox2/testcases/sanitizer.cc new file mode 100644 index 0000000..433dd09 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/sanitizer.cc @@ -0,0 +1,82 @@ +// 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 binary that tests for opened or closed file descriptors as specified. + +#include +#include + +#include +#include +#include +#include + +// The fd provided should not be open. +void TestClosedFd(int fd) { + int ret = fcntl(fd, F_GETFD); + if (ret != -1) { + printf("FAILURE: FD:%d is not closed\n", fd); + exit(EXIT_FAILURE); + } + if (errno != EBADF) { + printf( + "FAILURE: fcntl(%d) returned errno=%d (%s), should have " + "errno=EBADF/%d (%s)\n", + fd, errno, strerror(errno), EBADF, strerror(EBADF)); + exit(EXIT_FAILURE); + } +} + +// The fd provided should be open. +void TestOpenFd(int fd) { + int ret = fcntl(fd, F_GETFD); + if (ret == -1) { + printf("FAILURE: fcntl(%d) returned -1 with errno=%d (%s)\n", fd, errno, + strerror(errno)); + exit(EXIT_FAILURE); + } +} + +int main(int argc, char** argv) { + // Disable caching. + setbuf(stdin, nullptr); + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + for (int i = 0; i <= INR_OPEN_MAX; i++) { + bool should_be_closed = true; + for (int j = 0; j < argc; j++) { + errno = 0; + int cur_fd = strtol(argv[j], nullptr, 10); // NOLINT + if (errno != 0) { + printf("FAILURE: strtol('%s') failed\n", argv[j]); + exit(EXIT_FAILURE); + } + if (i == cur_fd) { + should_be_closed = false; + } + } + + printf("%d:%c ", i, should_be_closed ? 'C' : 'O'); + + if (should_be_closed) { + TestClosedFd(i); + } else { + TestOpenFd(i); + } + } + + printf("OK: All tests went OK\n"); + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/testcases/sleep.cc b/sandboxed_api/sandbox2/testcases/sleep.cc new file mode 100644 index 0000000..39f923c --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/sleep.cc @@ -0,0 +1,20 @@ +// 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 + +int main(int argc, char** argv) { + sleep(10); + return 0; +} diff --git a/sandboxed_api/sandbox2/testcases/symbolize.cc b/sandboxed_api/sandbox2/testcases/symbolize.cc new file mode 100644 index 0000000..c0279c9 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/symbolize.cc @@ -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. + +// A binary that crashes, either directly or by copying and re-executing, +// to test the stack tracing symbolizer. + +#include +#include +#include +#include +#include + +#include + +#include "absl/base/attributes.h" +#include "absl/strings/numbers.h" +#include "sandboxed_api/sandbox2/util/temp_file.h" +#include "sandboxed_api/util/raw_logging.h" + +ABSL_ATTRIBUTE_NOINLINE +void CrashMe() { + char* null = reinterpret_cast(0); + *null = 0; +} + +void RunWritable() { + int exe_fd = open("/proc/self/exe", O_RDONLY); + SAPI_RAW_PCHECK(exe_fd >= 0, "Opening /proc/self/exe"); + + std::string tmpname; + int tmp_fd; + std::tie(tmpname, tmp_fd) = sandbox2::CreateNamedTempFile("tmp").ValueOrDie(); + SAPI_RAW_PCHECK(fchmod(tmp_fd, S_IRWXU) == 0, "Fchmod on temporary file"); + + char buf[4096]; + while (true) { + ssize_t read_cnt = read(exe_fd, buf, sizeof(buf)); + SAPI_RAW_PCHECK(read_cnt >= 0, "Reading /proc/self/exe"); + if (read_cnt == 0) { + break; + } + SAPI_RAW_PCHECK(write(tmp_fd, buf, read_cnt) == read_cnt, + "Writing temporary file"); + } + + SAPI_RAW_PCHECK(close(tmp_fd) == 0, "Closing temporary file"); + tmp_fd = open(tmpname.c_str(), O_RDONLY); + SAPI_RAW_PCHECK(tmp_fd >= 0, "Reopening temporary file"); + + char prog_name[] = "crashme"; + char testno[] = "1"; + char* argv[] = {prog_name, testno, nullptr}; + + SAPI_RAW_PCHECK(execv(tmpname.c_str(), argv) == 0, "Executing copied binary"); +} + +int main(int argc, char** argv) { + if (argc < 2) { + printf("argc < 3\n"); + return EXIT_FAILURE; + } + + int testno; + SAPI_RAW_CHECK(absl::SimpleAtoi(argv[1], &testno), "testno not a number"); + switch (testno) { + case 1: + CrashMe(); + break; + case 2: + RunWritable(); + break; + default: + printf("Unknown test: %d\n", testno); + return EXIT_FAILURE; + } + + printf("OK: All tests went OK\n"); + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/testcases/tsync.cc b/sandboxed_api/sandbox2/testcases/tsync.cc new file mode 100644 index 0000000..5867ff6 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/tsync.cc @@ -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. + +// A binary that starts a thread then calls SandboxMeHere. +// It is used to test tsync support. + +#include +#include + +#include + +#include "sandboxed_api/sandbox2/client.h" +#include "sandboxed_api/sandbox2/comms.h" + +static pthread_barrier_t g_barrier; + +void* Sleepy(void*) { + pthread_barrier_wait(&g_barrier); + while (true) { + printf("hello from thread\n"); + sleep(1); + } +} + +int main(int argc, char** argv) { + pthread_t thread; + + if (pthread_barrier_init(&g_barrier, nullptr, 2) < 0) { + fprintf(stderr, "pthread_barrier_init: error\n"); + return EXIT_FAILURE; + } + + if (pthread_create(&thread, nullptr, Sleepy, nullptr)) { + fprintf(stderr, "pthread_create: error\n"); + return EXIT_FAILURE; + } + + printf("hello from main\n"); + + // Wait to make sure that the sleepy-thread is up and running. + pthread_barrier_wait(&g_barrier); + + sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD); + sandbox2::Client sandbox2_client(&comms); + sandbox2_client.SandboxMeHere(); + + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/testing.cc b/sandboxed_api/sandbox2/testing.cc new file mode 100644 index 0000000..dc43826 --- /dev/null +++ b/sandboxed_api/sandbox2/testing.cc @@ -0,0 +1,37 @@ +// 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/testing.h" + +#include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/util/path.h" + +namespace sandbox2 { + +std::string GetTestTempPath(absl::string_view name) { + // When using Bazel, the environment variable TEST_TMPDIR is guaranteed to be + // set. + // See https://docs.bazel.build/versions/master/test-encyclopedia.html for + // details. + return file::JoinPath(getenv("TEST_TMPDIR"), name); +} + +std::string GetTestSourcePath(absl::string_view name) { + // Like in GetTestTempPath(), when using Bazel, the environment variable + // TEST_SRCDIR is guaranteed to be set. + return file::JoinPath(getenv("TEST_SRCDIR"), + "com_google_sandboxed_api/sandboxed_api", name); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/testing.h b/sandboxed_api/sandbox2/testing.h new file mode 100644 index 0000000..d5cd673 --- /dev/null +++ b/sandboxed_api/sandbox2/testing.h @@ -0,0 +1,69 @@ +// 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_TESTING_H_ +#define SANDBOXED_API_SANDBOX2_TESTING_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +// The macro SKIP_SANITIZERS_AND_COVERAGE can be used in tests to skip running +// a given test (by emitting 'return') when running under one of the sanitizers +// (ASan, MSan, TSan) or under code coverage. Example: +// +// TEST(Foo, Bar) { +// SKIP_SANITIZERS_AND_COVERAGE; +// [...] +// } +// +// The reason for this is because Bazel options are inherited to binaries in +// data dependencies and cannot be per-target, which means when running a test +// with a sanitizer or coverage, the sandboxee as data dependency will also be +// compiled with sanitizer or coverage, which creates a lot of side effects and +// violates the sandbox policy prepared for the test. +// See b/7981124. // google3-only(internal reference) +// In other words, those tests cannot work under sanitizers or coverage, so we +// skip them in such situation using this macro. +// +// The downside of this approach is that no coverage will be collected. +// To still have coverage, pre-compile sandboxees and add them as test data, +// then no need to skip tests. +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) +#define SKIP_SANITIZERS_AND_COVERAGE return +#else +#define SKIP_SANITIZERS_AND_COVERAGE \ + do { \ + if (getenv("COVERAGE") != nullptr) { \ + return; \ + } \ + } while (0) +#endif + +namespace sandbox2 { + +// Returns a writable path usable in tests. If the name argument is specified, +// returns a name under that path. This can then be used for creating temporary +// test files and/or directories. +std::string GetTestTempPath(absl::string_view name = {}); + +// Returns a filename relative to the sandboxed_api directory at the root of the +// source tree. Use this to access data files in tests. +std::string GetTestSourcePath(absl::string_view name); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_TESTING_H_ diff --git a/sandboxed_api/sandbox2/unwind/BUILD.bazel b/sandboxed_api/sandbox2/unwind/BUILD.bazel new file mode 100644 index 0000000..375f1fa --- /dev/null +++ b/sandboxed_api/sandbox2/unwind/BUILD.bazel @@ -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. + +package(default_visibility = [ + "//sandboxed_api/sandbox2:__subpackages__", +]) + +licenses(["notice"]) # Apache 2.0 + +load("//sandboxed_api/bazel:proto.bzl", "sapi_proto_library") + +cc_library( + name = "ptrace_hook", + srcs = ["ptrace_hook.cc"], + hdrs = ["ptrace_hook.h"], +) + +cc_library( + name = "unwind", + srcs = ["unwind.cc"], + hdrs = ["unwind.h"], + copts = [ + # TODO(cblichmann): Remove this, fix bazel/external/libunwind.BUILD + "-Iexternal/org_gnu_libunwind/include", + ] + [ + "-D{symbol}={symbol}_wrapped".format(symbol = symbol) + for symbol in [ + "_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", + ] + ], + deps = [ + ":unwind_proto_cc", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2/util:maps_parser", + "//sandboxed_api/sandbox2/util:minielf", + "//sandboxed_api/sandbox2/util:strerror", + "//sandboxed_api/util:raw_logging", + "@com_google_absl//absl/strings", + "@org_gnu_libunwind//:unwind-ptrace-wrapped", + ], +) + +sapi_proto_library( + name = "unwind_proto", + srcs = ["unwind.proto"], +) diff --git a/sandboxed_api/sandbox2/unwind/ptrace_hook.cc b/sandboxed_api/sandbox2/unwind/ptrace_hook.cc new file mode 100644 index 0000000..7a737ce --- /dev/null +++ b/sandboxed_api/sandbox2/unwind/ptrace_hook.cc @@ -0,0 +1,100 @@ +// 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/unwind/ptrace_hook.h" + +#include +#include +#include + +#include +#include +#include + +// Maximum register struct size: 128 u64. +constexpr size_t kRegisterBufferSize = 128 * 8; +// Contains the register values in a ptrace specified format. +// This format is pretty opaque which is why we just forward +// the raw bytes (up to a certain limit). +static unsigned char register_values[kRegisterBufferSize]; +static size_t n_register_values_bytes_used = 0; + +// It should not be necessary to put this in a thread local storage as we +// do not support setting up the forkserver when there is more than one thread. +// However there might be some edge-cases, so we do this just in case. +thread_local bool emulate_ptrace = false; + +void ArmPtraceEmulation() { emulate_ptrace = true; } + +void InstallUserRegs(const char *ptr, size_t size) { + if (sizeof(register_values) < size) { + fprintf(stderr, "install_user_regs: Got more bytes than supported (%lu)\n", + size); + } else { + memcpy(®ister_values, ptr, size); + n_register_values_bytes_used = size; + } +} + +// Replaces the libc version of ptrace. +// This wrapper makes use of process_vm_readv to read process memory instead of +// issuing ptrace syscalls. Accesses to registers will be emulated, for this the +// register values should be set via install_user_regs(). +// The emulation can be switched on using arm_ptrace_emulation(). +extern "C" long int ptrace_wrapped( // NOLINT + enum __ptrace_request request, pid_t pid, void *addr, void *data) { + // Register size is `long` for the supported architectures according to the + // kernel. + using reg_type = long; // NOLINT + constexpr size_t reg_size = sizeof(reg_type); + if (!emulate_ptrace) { + return ptrace(request, pid, addr, data); + } + switch (request) { + case PTRACE_PEEKDATA: { + long int read_data; // NOLINT + + struct iovec local, remote; + local.iov_len = sizeof(long int); // NOLINT + local.iov_base = &read_data; + + remote.iov_len = sizeof(long int); // NOLINT + remote.iov_base = addr; + + if (process_vm_readv(pid, &local, 1, &remote, 1, 0) > 0) { + return read_data; + } else { + return -1; + } + } break; + case PTRACE_PEEKUSER: { + uintptr_t next_offset = reinterpret_cast(addr) + reg_size; + // Make sure read is in-bounds and aligned. + if (next_offset <= n_register_values_bytes_used && + reinterpret_cast(addr) % reg_size == 0) { + return reinterpret_cast( + ®ister_values)[reinterpret_cast(addr) / reg_size]; + } else { + return -1; + } + } break; + default: { + fprintf(stderr, "ptrace-wrapper: forbidden operation invoked: %d\n", + request); + _exit(1); + } + } + + return 0; +} diff --git a/sandboxed_api/sandbox2/unwind/ptrace_hook.h b/sandboxed_api/sandbox2/unwind/ptrace_hook.h new file mode 100644 index 0000000..1110b48 --- /dev/null +++ b/sandboxed_api/sandbox2/unwind/ptrace_hook.h @@ -0,0 +1,26 @@ +// 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_UNWIND_PTRACE_HOOK_H_ +#define SANDBOXED_API_SANDBOX2_UNWIND_PTRACE_HOOK_H_ + +#include + +// Sets the register values that the ptrace emulation will return. +void InstallUserRegs(const char* ptr, size_t size); + +// Enables the ptrace emulation. +void ArmPtraceEmulation(); + +#endif // SANDBOXED_API_SANDBOX2_UNWIND_PTRACE_HOOK_H_ diff --git a/sandboxed_api/sandbox2/unwind/unwind.cc b/sandboxed_api/sandbox2/unwind/unwind.cc new file mode 100644 index 0000000..7987004 --- /dev/null +++ b/sandboxed_api/sandbox2/unwind/unwind.cc @@ -0,0 +1,205 @@ +// 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/unwind/unwind.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "external/org_gnu_libunwind/include/libunwind-ptrace.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/unwind/unwind.pb.h" +#include "sandboxed_api/sandbox2/util/maps_parser.h" +#include "sandboxed_api/sandbox2/util/minielf.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/raw_logging.h" + +namespace sandbox2 { +namespace { + +std::string GetSymbolAt(const std::map& addr_to_symbol, + uint64_t addr) { + auto entry_for_next_symbol = addr_to_symbol.lower_bound(addr); + if (entry_for_next_symbol != addr_to_symbol.end() && + entry_for_next_symbol != addr_to_symbol.begin()) { + // Matches the addr exactly: + if (entry_for_next_symbol->first == addr) { + return entry_for_next_symbol->second; + } + + // Might be inside a function, return symbol+offset; + const auto entry_for_previous_symbol = --entry_for_next_symbol; + if (!entry_for_previous_symbol->second.empty()) { + return absl::StrCat(entry_for_previous_symbol->second, "+0x", + absl::Hex(addr - entry_for_previous_symbol->first)); + } + } + return ""; +} + +} // namespace + +void GetIPList(pid_t pid, std::vector* ips, int max_frames) { + ips->clear(); + + unw_cursor_t cursor; + static unw_addr_space_t as = + unw_create_addr_space(&_UPT_accessors, 0 /* byte order */); + if (as == nullptr) { + SAPI_RAW_LOG(WARNING, "unw_create_addr_space() failed"); + return; + } + + auto* ui = reinterpret_cast(_UPT_create(pid)); + if (ui == nullptr) { + SAPI_RAW_LOG(WARNING, "_UPT_create() failed"); + return; + } + + int rc = unw_init_remote(&cursor, as, ui); + if (rc < 0) { + // Could be UNW_EINVAL (8), UNW_EUNSPEC (1) or UNW_EBADREG (3). + SAPI_RAW_LOG(WARNING, "unw_init_remote() failed with error %d", rc); + } else { + for (int i = 0; i < max_frames; i++) { + unw_word_t ip; + rc = unw_get_reg(&cursor, UNW_REG_IP, &ip); + if (rc < 0) { + // Could be UNW_EUNSPEC or UNW_EBADREG. + SAPI_RAW_LOG(WARNING, "unw_get_reg() failed with error %d", rc); + break; + } + ips->push_back(ip); + rc = unw_step(&cursor); + // Non-error condition: UNW_ESUCCESS (0). + if (rc < 0) { + // If anything but UNW_ESTOPUNWIND (-5), there has been an error. + // However since we can't do anything about it and it appears that + // this happens every time we don't log this. + break; + } + } + } + + // This is only needed if _UPT_create() has been successful. + _UPT_destroy(ui); +} + +void RunLibUnwindAndSymbolizer(pid_t pid, Comms* comms, int max_frames, + const std::string& delim) { + UnwindResult msg; + std::string stack_trace; + std::vector ips; + + RunLibUnwindAndSymbolizer(pid, &stack_trace, &ips, max_frames, delim); + for (const auto& i : ips) { + msg.add_ip(i); + } + msg.set_stacktrace(stack_trace.c_str(), stack_trace.size()); + comms->SendProtoBuf(msg); +} + +void RunLibUnwindAndSymbolizer(pid_t pid, std::string* stack_trace_out, + std::vector* ips, int max_frames, + const std::string& delim) { + // Run libunwind. + GetIPList(pid, ips, max_frames); + + // Open /proc/pid/maps. + std::string path_maps = absl::StrCat("/proc/", pid, "/maps"); + std::unique_ptr f(fopen(path_maps.c_str(), "r"), + [](FILE* s) { + if (s) { + fclose(s); + } + }); + if (!f) { + // Could not open maps file. + SAPI_RAW_LOG(ERROR, "Could not open %s", path_maps); + return; + } + + constexpr static size_t kBufferSize = 10 * 1024 * 1024; + std::string maps_content(kBufferSize, '\0'); + size_t bytes_read = fread(&maps_content[0], 1, kBufferSize, f.get()); + if (bytes_read == 0) { + // Could not read the whole maps file. + SAPI_RAW_PLOG(ERROR, "Could not read maps file"); + return; + } + maps_content.resize(bytes_read); + + auto maps_or = ParseProcMaps(maps_content); + if (!maps_or.ok()) { + SAPI_RAW_LOG(ERROR, "Could not parse /proc/%d/maps", pid); + return; + } + auto maps = std::move(maps_or).ValueOrDie(); + + // Get symbols for each file entry in the maps entry. + // This is not a very efficient way, so we might want to optimize it. + std::map addr_to_symbol; + for (const auto& entry : maps) { + if (!entry.path.empty()) { + // Store details about start + end of this map. + // The maps entries are ordered and thus sorted with increasing adresses. + // This means if there is a symbol @ entry.end, it will be overwritten in + // the next iteration. + addr_to_symbol[entry.start] = absl::StrCat("map:", entry.path); + addr_to_symbol[entry.end] = ""; + } + + if (!entry.path.empty() && entry.is_executable) { + auto elf_or = ElfFile::ParseFromFile(entry.path, ElfFile::kLoadSymbols); + if (!elf_or.ok()) { + SAPI_RAW_LOG(WARNING, "Could not load symbols for %s: %s", entry.path, + elf_or.status().message()); + continue; + } + auto elf = std::move(elf_or).ValueOrDie(); + + for (const auto& symbol : elf.symbols()) { + if (elf.position_independent()) { + if (symbol.address < entry.end - entry.start) { + addr_to_symbol[symbol.address + entry.start] = symbol.name; + } + } else { + if (symbol.address >= entry.start && symbol.address < entry.end) { + addr_to_symbol[symbol.address] = symbol.name; + } + } + } + } + } + + std::string stack_trace; + // Symbolize stacktrace. + for (auto i = ips->begin(); i != ips->end(); ++i) { + if (i != ips->begin()) { + stack_trace += delim; + } + std::string symbol = GetSymbolAt(addr_to_symbol, static_cast(*i)); + absl::StrAppend(&stack_trace, symbol, "(0x", absl::Hex(*i), ")"); + } + + *stack_trace_out = stack_trace; +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/unwind/unwind.h b/sandboxed_api/sandbox2/unwind/unwind.h new file mode 100644 index 0000000..7fe8f49 --- /dev/null +++ b/sandboxed_api/sandbox2/unwind/unwind.h @@ -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. + +#ifndef SANDBOXED_API_SANDBOX2_UNWIND_UNWIND_H_ +#define SANDBOXED_API_SANDBOX2_UNWIND_UNWIND_H_ + +#include + +#include +#include +#include + +#include "sandboxed_api/sandbox2/comms.h" + +namespace sandbox2 { + +void GetIPList(pid_t pid, std::vector* ips, int max_frames); +void RunLibUnwindAndSymbolizer(pid_t pid, Comms* comms, int max_frames, + const std::string& delim); + +void RunLibUnwindAndSymbolizer(pid_t pid, std::string* stack_trace_out, + std::vector* ips, int max_frames, + const std::string& delim); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UNWIND_UNWIND_H_ diff --git a/sandboxed_api/sandbox2/unwind/unwind.proto b/sandboxed_api/sandbox2/unwind/unwind.proto new file mode 100644 index 0000000..6af897c --- /dev/null +++ b/sandboxed_api/sandbox2/unwind/unwind.proto @@ -0,0 +1,39 @@ +// 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. + +// Proto for the communication between executor and sandboxed libunwind +syntax = "proto3"; + +package sandbox2; + +message UnwindSetup { + // Required + // Process ID of the process to unwind + uint64 pid = 1; + // Register content for the process to unwind + bytes regs = 2; + // Optional + // Maximum number of stack frames to unwind + uint64 default_max_frames = 3; + // Delimiter used in the result to separate the stack frames + bytes delim = 4; +} + +message UnwindResult { + // Readable stacktrace, symbolized, each framed separated by the delim + // specified in the UnwindSetup message + bytes stacktrace = 1; + // Stack frames + repeated uint64 ip = 2; +} diff --git a/sandboxed_api/sandbox2/util.cc b/sandboxed_api/sandbox2/util.cc new file mode 100644 index 0000000..5f61496 --- /dev/null +++ b/sandboxed_api/sandbox2/util.cc @@ -0,0 +1,306 @@ +// 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/util.h" + +#include // __NR_memdfd_create +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/attributes.h" +#include "absl/strings/escaping.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/strings/strip.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/path.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/raw_logging.h" +#include "sandboxed_api/util/canonical_errors.h" + +namespace sandbox2 { +namespace util { + +void CharPtrArrToVecString(char* const* arr, std::vector* vec) { + for (int i = 0; arr[i]; ++i) { + vec->push_back(arr[i]); + } +} + +const char** VecStringToCharPtrArr(const std::vector& vec) { + const int vec_size = vec.size(); + const char** arr = new const char*[vec_size + 1]; + for (int i = 0; i < vec_size; ++i) { + arr[i] = vec[i].c_str(); + } + arr[vec_size] = nullptr; + return arr; +} + +std::string GetProgName(pid_t pid) { + std::string fname = file::JoinPath("/proc", absl::StrCat(pid), "exe"); + // Use ReadLink instead of RealPath, as for fd-based executables (e.g. created + // via memfd_create()) the RealPath will not work, as the destination file + // doesn't exist on the local file-system. + return file_util::fileops::Basename(file_util::fileops::ReadLink(fname)); +} + +long Syscall(long sys_no, // NOLINT + uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, + uintptr_t a5, uintptr_t a6) { + return syscall(sys_no, a1, a2, a3, a4, a5, a6); +} + +bool CreateDirRecursive(const std::string& path, mode_t mode) { + int error = mkdir(path.c_str(), mode); + + if (error == 0 || errno == EEXIST) { + return true; + } + + // We couldn't create the dir for reasons we can't handle. + if (errno != ENOENT) { + return false; + } + + // The EEXIST case, the parent directory doesn't exist yet. + // Let's create it. + const std::string dir = file_util::fileops::StripBasename(path); + if (dir == "/" || dir.empty()) { + return false; + } + if (!CreateDirRecursive(dir, mode)) { + return false; + } + + // Now the parent dir exists, retry creating the directory. + error = mkdir(path.c_str(), mode); + + return error == 0; +} + +namespace { + +int ChildFunc(void* arg) { + auto* env_ptr = reinterpret_cast(arg); + // Restore the old stack. + longjmp(*env_ptr, 1); +} + +// This code is inspired by base/process/launch_posix.cc in the Chromium source. +// There are a few things to be careful of here: +// - Make sure the stack_buf is below the env_ptr to please FORTIFY_SOURCE. +// - Make sure the stack_buf is not too far away from the real stack to please +// ASAN. If they are too far away, a warning is printed. This means not only +// that the temporary stack buffer needs to also be on the stack, but also that +// we need to disable ASAN for this function, to prevent it from being placed on +// the fake ASAN stack. +// - Make sure that the buffer is aligned to whatever is required by the CPU. +ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS +ABSL_ATTRIBUTE_NOINLINE +pid_t CloneAndJump(int flags, jmp_buf* env_ptr) { + uint8_t stack_buf[PTHREAD_STACK_MIN] ABSL_CACHELINE_ALIGNED; +#if defined(__x86_64__) || defined(__x86__) || defined(__i386__) || \ + defined(__powerpc64__) + // Stack grows down. + void* stack = stack_buf + sizeof(stack_buf); +#else +#error "Architecture is not supported" +#endif + int r = clone(&ChildFunc, stack, flags, env_ptr, nullptr, nullptr, nullptr); + if (r == -1) { + SAPI_RAW_PLOG(ERROR, "clone()"); + } + return r; +} + +} // namespace + +pid_t ForkWithFlags(int flags) { + const int unsupported_flags = CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | + CLONE_PARENT_SETTID | CLONE_SETTLS | CLONE_VM; + if (flags & unsupported_flags) { + SAPI_RAW_LOG(ERROR, "ForkWithFlags used with unsupported flag"); + return -1; + } + + jmp_buf env; + if (setjmp(env) == 0) { + return CloneAndJump(flags, &env); + } + + // Child. + return 0; +} + +bool CreateMemFd(int* fd, const char* name) { + // Usually defined in linux/memfd.h. Define it here to avoid dependency on + // UAPI headers. + constexpr uintptr_t MFD_CLOEXEC = 0x0001U; + int tmp_fd = Syscall(__NR_memfd_create, reinterpret_cast(name), + MFD_CLOEXEC); + if (tmp_fd < 0) { + if (errno == ENOSYS) { + SAPI_RAW_LOG(ERROR, + "This system does not seem to support the memfd_create()" + " syscall. Try running on a newer kernel."); + } else { + SAPI_RAW_PLOG(ERROR, "Could not create tmp file '%s'", name); + } + return false; + } + *fd = tmp_fd; + return true; +} + +::sapi::StatusOr Communicate(const std::vector& argv, + const std::vector& envv, + std::string* output) { + int cout_pipe[2]; + posix_spawn_file_actions_t action; + + if (pipe(cout_pipe) == -1) { + return ::sapi::UnknownError( + absl::StrCat("creating pipe: ", StrError(errno))); + } + file_util::fileops::FDCloser cout_closer{cout_pipe[1]}; + + posix_spawn_file_actions_init(&action); + struct ActionCleanup { + ~ActionCleanup() { posix_spawn_file_actions_destroy(action_); } + posix_spawn_file_actions_t* action_; + } action_cleanup{&action}; + + // Redirect both stdout and stderr to stdout to our pipe. + posix_spawn_file_actions_addclose(&action, cout_pipe[0]); + posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1); + posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 2); + posix_spawn_file_actions_addclose(&action, cout_pipe[1]); + + char** args = const_cast(util::VecStringToCharPtrArr(argv)); + char** envp = const_cast(util::VecStringToCharPtrArr(envv)); + struct ArgumentCleanup { + ~ArgumentCleanup() { + delete[] args_; + delete[] envp_; + } + char** args_; + char** envp_; + } args_cleanup{args, envp}; + + pid_t pid; + if (posix_spawnp(&pid, args[0], &action, nullptr, args, envp) != 0) { + return ::sapi::UnknownError( + absl::StrCat("posix_spawnp() failed: ", StrError(errno))); + } + + // Close child end of the pipe. + cout_closer.Close(); + + std::string buffer(1024, '\0'); + for (;;) { + int bytes_read = + TEMP_FAILURE_RETRY(read(cout_pipe[0], &buffer[0], buffer.length())); + if (bytes_read < 0) { + return ::sapi::InternalError( + absl::StrCat("reading from cout pipe failed: ", StrError(errno))); + } + if (bytes_read == 0) { + break; // Nothing left to read + } + absl::StrAppend(output, absl::string_view(buffer.data(), bytes_read)); + } + + int status; + SAPI_RAW_PCHECK(TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)) == pid, + "Waiting for subprocess"); + return WEXITSTATUS(status); +} + +std::string GetSignalName(int signo) { + constexpr absl::string_view kSignalNames[] = { + "SIG_0", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", + "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", + "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", + "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", + "SIGXCPU", "SIGXFSZ", "SIGVTALARM", "SIGPROF", "SIGWINCH", "SIGIO", + "SIGPWR", "SIGSYS"}; + + if (signo >= SIGRTMIN && signo <= SIGRTMAX) { + return absl::StrFormat("SIGRT-%d [%d]", signo - SIGRTMIN, signo); + } + if (signo < 0 || signo >= static_cast(ABSL_ARRAYSIZE(kSignalNames))) { + return absl::StrFormat("UNKNOWN_SIGNAL [%d]", signo); + } + return absl::StrFormat("%s [%d]", kSignalNames[signo], signo); +} + +::sapi::StatusOr ReadCPathFromPid(pid_t pid, uintptr_t ptr) { + std::string path(PATH_MAX, '\0'); + iovec local_iov[] = {{&path[0], path.size()}}; + + static const uintptr_t page_size = getpagesize(); + static const uintptr_t page_mask = ~(page_size - 1); + // See 'man process_vm_readv' for details on how to read NUL-terminated + // strings with this syscall. + size_t len1 = ((ptr + page_size) & page_mask) - ptr; + len1 = (len1 > path.size()) ? path.size() : len1; + size_t len2 = (path.size() <= len1) ? 0UL : path.size() - len1; + // Second iov is wrapping around to NULL ptr. + if ((ptr + len1) < ptr) { + len2 = 0UL; + } + + iovec remote_iov[] = { + {reinterpret_cast(ptr), len1}, + {reinterpret_cast(ptr + len1), len2}, + }; + + SAPI_RAW_VLOG(4, "ReadCPathFromPid (iovec): len1: %d, len2: %d", len1, len2); + ssize_t sz = process_vm_readv(pid, local_iov, ABSL_ARRAYSIZE(local_iov), + remote_iov, ABSL_ARRAYSIZE(remote_iov), 0); + if (sz < 0) { + return ::sapi::InternalError(absl::StrFormat( + "process_vm_readv() failed for PID: %d at address: %#x: %s", pid, + reinterpret_cast(ptr), StrError(errno))); + } + + // Check for whether there's a NUL byte in the buffer. If not, it's an + // incorrect path (or >PATH_MAX). + auto pos = path.find('\0'); + if (pos == std::string::npos) { + return ::sapi::FailedPreconditionError(absl::StrCat( + "No NUL-byte inside the C std::string '", absl::CHexEscape(path), "'")); + } + path.resize(pos); + return path; +} + +} // namespace util +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util.h b/sandboxed_api/sandbox2/util.h new file mode 100644 index 0000000..b75bf54 --- /dev/null +++ b/sandboxed_api/sandbox2/util.h @@ -0,0 +1,79 @@ +// 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::util namespace provides various, uncategorized, functions +// useful for creating sandboxes. + +#ifndef SANDBOXED_API_SANDBOX2_UTIL_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_H_ + +#include + +#include +#include +#include + +#include "absl/base/macros.h" +#include "sandboxed_api/util/statusor.h" + +namespace sandbox2 { +namespace util { + +// Converts an array of char* (terminated by a nullptr, like argv, or environ +// arrays), to an std::vector. +void CharPtrArrToVecString(char* const* arr, std::vector* vec); + +// Converts a vector of strings to a newly allocated array. The array is limited +// by the terminating nullptr entry (like environ or argv). It must be freed by +// the caller. +const char** VecStringToCharPtrArr(const std::vector& vec); + +// Returns the program name (via /proc/self/comm) for a given PID. +std::string GetProgName(pid_t pid); + +// Invokes a syscall, avoiding on-stack argument promotion, as it might happen +// with vararg syscall() function. +long Syscall(long sys_no, // NOLINT + uintptr_t a1 = 0, uintptr_t a2 = 0, uintptr_t a3 = 0, + uintptr_t a4 = 0, uintptr_t a5 = 0, uintptr_t a6 = 0); + +// Recursively creates a directory, skipping segments that already exist. +bool CreateDirRecursive(const std::string& path, mode_t mode); + +// Fork based on clone() which updates glibc's PID/TID caches - Based on: +// https://chromium.googlesource.com/chromium/src/+/9eb564175dbd452196f782da2b28e3e8e79c49a5%5E!/ +// +// Return values as for 'man 2 fork'. +pid_t ForkWithFlags(int flags); + +// Creates a new memfd. +bool CreateMemFd(int* fd, const char* name = "buffer_file"); + +// Executes a the program given by argv and the specified environment and +// captures any output to stdout/stderr. +::sapi::StatusOr Communicate(const std::vector& argv, + const std::vector& envv, + std::string* output); + +// Returns signal description. +std::string GetSignalName(int signo); + +// Reads a path string (NUL-terminated, shorter than PATH_MAX) from another +// process memory +::sapi::StatusOr ReadCPathFromPid(pid_t pid, uintptr_t ptr); + +} // namespace util +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UTIL_H_ diff --git a/sandboxed_api/sandbox2/util/BUILD.bazel b/sandboxed_api/sandbox2/util/BUILD.bazel new file mode 100644 index 0000000..b576264 --- /dev/null +++ b/sandboxed_api/sandbox2/util/BUILD.bazel @@ -0,0 +1,214 @@ +# 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. + +package(default_visibility = [ + "//sandboxed_api:__subpackages__", + "//security/gfence:__subpackages__", +]) + +licenses(["notice"]) # Apache 2.0 + +cc_library( + name = "bpf_helper", + srcs = ["bpf_helper.c"], + hdrs = ["bpf_helper.h"], +) + +# String file routines +cc_library( + name = "file_helpers", + srcs = ["file_helpers.cc"], + hdrs = ["file_helpers.h"], + deps = [ + "//sandboxed_api/util:status", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "file_helpers_test", + size = "small", + srcs = ["file_helpers_test.cc"], + deps = [ + ":file_helpers", + "//sandboxed_api/util:status_matchers", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +# Portable filesystem and path utility functions +cc_library( + name = "fileops", + srcs = ["fileops.cc"], + hdrs = ["fileops.h"], + copts = [ + "-Wno-deprecated-declarations", # readdir64_r + ], + deps = [ + ":strerror", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "fileops_test", + size = "small", + srcs = ["fileops_test.cc"], + deps = [ + ":file_helpers", + ":fileops", + "//sandboxed_api/sandbox2:testing", + "//sandboxed_api/util:status_matchers", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +# Another helper library with filesystem and path utility functions. +cc_library( + name = "file_base", + srcs = ["path.cc"], + hdrs = ["path.h"], + deps = [ + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "file_base_test", + size = "small", + srcs = ["path_test.cc"], + deps = [ + ":file_base", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +# Internal thread-safe helper to format system error messages. Mostly +# equivalent to base/strerror.h. +cc_library( + name = "strerror", + srcs = ["strerror.cc"], + hdrs = ["strerror.h"], + deps = [ + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "strerror_test", + srcs = ["strerror_test.cc"], + deps = [ + ":strerror", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "minielf", + srcs = ["minielf.cc"], + hdrs = ["minielf.h"], + deps = [ + ":strerror", + "//sandboxed_api/sandbox2:util", + "//sandboxed_api/util:raw_logging", + "//sandboxed_api/util:status", + "//sandboxed_api/util:statusor", + "@com_google_absl//absl/base:endian", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "minielf_test", + srcs = ["minielf_test.cc"], + data = [ + ":testdata/chrome_grte_header", + ":testdata/hello_world", + ], + features = ["-dynamic_link_test_srcs"], # see go/dynamic_link_test_srcs + deps = [ + ":maps_parser", + ":minielf", + "//sandboxed_api/sandbox2:testing", + "//sandboxed_api/util:status_matchers", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "temp_file", + srcs = ["temp_file.cc"], + hdrs = ["temp_file.h"], + deps = [ + ":fileops", + ":strerror", + "//sandboxed_api/util:status", + "//sandboxed_api/util:statusor", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "temp_file_test", + srcs = ["temp_file_test.cc"], + deps = [ + ":file_base", + ":fileops", + ":temp_file", + "//sandboxed_api/sandbox2:testing", + "//sandboxed_api/util:status_matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "maps_parser", + srcs = ["maps_parser.cc"], + hdrs = ["maps_parser.h"], + deps = [ + "//sandboxed_api/util:status", + "//sandboxed_api/util:statusor", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "maps_parser_test", + srcs = ["maps_parser_test.cc"], + deps = [ + ":maps_parser", + "//sandboxed_api/util:status_matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "runfiles", + srcs = ["runfiles.cc"], + hdrs = ["runfiles.h"], + deps = [ + ":file_base", + "//sandboxed_api/util:flag", + "//sandboxed_api/util:raw_logging", + "@bazel_tools//tools/cpp/runfiles", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) diff --git a/sandboxed_api/sandbox2/util/bpf_helper.c b/sandboxed_api/sandbox2/util/bpf_helper.c new file mode 100644 index 0000000..cd88ed2 --- /dev/null +++ b/sandboxed_api/sandbox2/util/bpf_helper.c @@ -0,0 +1,110 @@ +// 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. + +// Seccomp BPF helper functions, modified from the Chromium OS version. The +// original notice is below. +// +// Copyright (c) 2012 The Chromium OS Authors +// Author: Will Drewry +// +// The code may be used by anyone for any purpose, +// and can serve as a starting point for developing +// applications using prctl(PR_ATTACH_SECCOMP_FILTER). + + +#include "sandboxed_api/sandbox2/util/bpf_helper.h" + +#include +#include +#include + +int bpf_resolve_jumps(struct bpf_labels *labels, + struct sock_filter *filter, size_t count) +{ + size_t i; + + if (count < 1 || count > BPF_MAXINSNS) + return -1; + /* + * Walk it once, backwards, to build the label table and do fixups. + * Since backward jumps are disallowed by BPF, this is easy. + */ + for (i = 0; i < count; ++i) { + size_t offset = count - i - 1; + struct sock_filter *instr = &filter[offset]; + if (instr->code != (BPF_JMP+BPF_JA)) + continue; + switch ((instr->jt<<8)|instr->jf) { + case (JUMP_JT<<8)|JUMP_JF: + if (labels->labels[instr->k].location == 0xffffffff) { + fprintf(stderr, "Unresolved label: '%s'\n", + labels->labels[instr->k].label); + return 1; + } + instr->k = labels->labels[instr->k].location - + (offset + 1); + instr->jt = 0; + instr->jf = 0; + continue; + case (LABEL_JT<<8)|LABEL_JF: + if (labels->labels[instr->k].location != 0xffffffff) { + fprintf(stderr, "Duplicate label use: '%s'\n", + labels->labels[instr->k].label); + return 1; + } + labels->labels[instr->k].location = offset; + instr->k = 0; /* fall through */ + instr->jt = 0; + instr->jf = 0; + continue; + } + } + return 0; +} + +/* Simple lookup table for labels. */ +__u32 seccomp_bpf_label(struct bpf_labels *labels, const char *label) +{ + struct __bpf_label *begin = labels->labels, *end; + int id; + + if (labels->count == BPF_LABELS_MAX) { + fprintf(stderr, "Too many labels\n"); + exit(1); + } + if (labels->count == 0) { + begin->label = label; + begin->location = 0xffffffff; + labels->count++; + return 0; + } + end = begin + labels->count; + for (id = 0; begin < end; ++begin, ++id) { + if (!strcmp(label, begin->label)) + return id; + } + begin->label = label; + begin->location = 0xffffffff; + labels->count++; + return id; +} + +void seccomp_bpf_print(struct sock_filter *filter, size_t count) +{ + struct sock_filter *end = filter + count; + for ( ; filter < end; ++filter) + printf("{ code=%u,jt=%u,jf=%u,k=%u },\n", + filter->code, filter->jt, filter->jf, filter->k); +} + diff --git a/sandboxed_api/sandbox2/util/bpf_helper.h b/sandboxed_api/sandbox2/util/bpf_helper.h new file mode 100644 index 0000000..77f9e61 --- /dev/null +++ b/sandboxed_api/sandbox2/util/bpf_helper.h @@ -0,0 +1,299 @@ +// 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. + +// Wrapper around BPF macros, modified from the Chromium OS version. The +// original notice is below. +// +// Copyright (c) 2012 The Chromium OS Authors +// Author: Will Drewry +// +// The code may be used by anyone for any purpose, +// and can serve as a starting point for developing +// applications using prctl(PR_SET_SECCOMP, 2, ...). +// +// No guarantees are provided with respect to the correctness +// or functionality of this code. + +#ifndef SANDBOXED_API_SANDBOX2_UTIL_BPF_HELPER_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_BPF_HELPER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* for __BITS_PER_LONG */ +#include +#include +#include /* for seccomp_data */ +#include +#include +#include + +#define BPF_LABELS_MAX 256 +struct bpf_labels { + int count; + struct __bpf_label { + const char *label; + __u32 location; + } labels[BPF_LABELS_MAX]; +}; + +int bpf_resolve_jumps(struct bpf_labels *labels, + struct sock_filter *filter, size_t count); +__u32 seccomp_bpf_label(struct bpf_labels *labels, const char *label); +void seccomp_bpf_print(struct sock_filter *filter, size_t count); + +#define JUMP_JT 0xff +#define JUMP_JF 0xff +#define LABEL_JT 0xfe +#define LABEL_JF 0xfe + +#define DENY \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) +/* A synonym of of DENY */ +#define KILL \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) +#define TRAP(val) \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRAP | (val & SECCOMP_RET_DATA)) +#define ERRNO(val) \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO | (val & SECCOMP_RET_DATA)) +#define TRACE(val) \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRACE | (val & SECCOMP_RET_DATA)) +#define ALLOW \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) + +#define JUMP(labels, label) \ + BPF_JUMP(BPF_JMP+BPF_JA, FIND_LABEL((labels), (label)), \ + JUMP_JT, JUMP_JF) +#define LABEL(labels, label) \ + BPF_JUMP(BPF_JMP+BPF_JA, FIND_LABEL((labels), (label)), \ + LABEL_JT, LABEL_JF) +#define SYSCALL(nr, jt) \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (nr), 0, 1), \ + jt + +/* Lame, but just an example */ +#define FIND_LABEL(labels, label) seccomp_bpf_label((labels), #label) + +#define EXPAND(...) __VA_ARGS__ + +/* Ensure that we load the logically correct offset. */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) + sizeof(__u32) +#else +#error "Unknown endianness" +#endif + +/* Map all width-sensitive operations */ +#if __BITS_PER_LONG == 32 + +#define JEQ(x, jt) JEQ32(x, EXPAND(jt)) +#define JNE(x, jt) JNE32(x, EXPAND(jt)) +#define JGT(x, jt) JGT32(x, EXPAND(jt)) +#define JLT(x, jt) JLT32(x, EXPAND(jt)) +#define JGE(x, jt) JGE32(x, EXPAND(jt)) +#define JLE(x, jt) JLE32(x, EXPAND(jt)) +#define JA(x, jt) JA32(x, EXPAND(jt)) +#define ARG(i) ARG_32(i) + +#elif __BITS_PER_LONG == 64 + +/* Ensure that we load the logically correct offset. */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define ENDIAN(_lo, _hi) _lo, _hi +#define HI_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) + sizeof(__u32) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define ENDIAN(_lo, _hi) _hi, _lo +#define HI_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) +#endif + +union arg64 { + struct { + __u32 ENDIAN(lo32, hi32); + }; + __u64 u64; +}; + +#define JEQ(x, jt) \ + JEQ64(((union arg64){.u64 = (x)}).lo32, \ + ((union arg64){.u64 = (x)}).hi32, \ + EXPAND(jt)) +#define JGT(x, jt) \ + JGT64(((union arg64){.u64 = (x)}).lo32, \ + ((union arg64){.u64 = (x)}).hi32, \ + EXPAND(jt)) +#define JGE(x, jt) \ + JGE64(((union arg64){.u64 = (x)}).lo32, \ + ((union arg64){.u64 = (x)}).hi32, \ + EXPAND(jt)) +#define JNE(x, jt) \ + JNE64(((union arg64){.u64 = (x)}).lo32, \ + ((union arg64){.u64 = (x)}).hi32, \ + EXPAND(jt)) +#define JLT(x, jt) \ + JLT64(((union arg64){.u64 = (x)}).lo32, \ + ((union arg64){.u64 = (x)}).hi32, \ + EXPAND(jt)) +#define JLE(x, jt) \ + JLE64(((union arg64){.u64 = (x)}).lo32, \ + ((union arg64){.u64 = (x)}).hi32, \ + EXPAND(jt)) + +#define JA(x, jt) \ + JA64(((union arg64){.u64 = (x)}).lo32, \ + ((union arg64){.u64 = (x)}).hi32, \ + EXPAND(jt)) +#define ARG(i) ARG_64(i) + +#else +#error __BITS_PER_LONG value unusable. +#endif + +/* Loads the arg into A */ +#define ARG_32(idx) \ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, LO_ARG(idx)) + +/* Loads lo into M[0] and hi into M[1] and A */ +#define ARG_64(idx) \ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, LO_ARG(idx)), \ + BPF_STMT(BPF_ST, 0), /* lo -> M[0] */ \ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, HI_ARG(idx)), \ + BPF_STMT(BPF_ST, 1) /* hi -> M[1] */ + +#define JEQ32(value, jt) \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (value), 0, 1), \ + jt + +#define JNE32(value, jt) \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (value), 1, 0), \ + jt + +#define JA32(value, jt) \ + BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, (value), 0, 1), \ + jt + +#define JGE32(value, jt) \ + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (value), 0, 1), \ + jt + +#define JGT32(value, jt) \ + BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (value), 0, 1), \ + jt + +#define JLE32(value, jt) \ + BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (value), 1, 0), \ + jt + +#define JLT32(value, jt) \ + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (value), 1, 0), \ + jt + +/* + * All the JXX64 checks assume lo is saved in M[0] and hi is saved in both + * A and M[1]. This invariant is kept by restoring A if necessary. + */ +#define JEQ64(lo, hi, jt) \ + /* if (hi != arg.hi) goto NOMATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \ + BPF_STMT(BPF_LD+BPF_MEM, 0), /* swap in lo */ \ + /* if (lo != arg.lo) goto NOMATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (lo), 0, 2), \ + BPF_STMT(BPF_LD+BPF_MEM, 1), \ + jt, \ + BPF_STMT(BPF_LD+BPF_MEM, 1) + +#define JNE64(lo, hi, jt) \ + /* if (hi != arg.hi) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 3), \ + BPF_STMT(BPF_LD+BPF_MEM, 0), \ + /* if (lo != arg.lo) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (lo), 2, 0), \ + BPF_STMT(BPF_LD+BPF_MEM, 1), \ + jt, \ + BPF_STMT(BPF_LD+BPF_MEM, 1) + +#define JA64(lo, hi, jt) \ + /* if (hi & arg.hi) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, (hi), 3, 0), \ + BPF_STMT(BPF_LD+BPF_MEM, 0), \ + /* if (lo & arg.lo) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JSET+BPF_K, (lo), 0, 2), \ + BPF_STMT(BPF_LD+BPF_MEM, 1), \ + jt, \ + BPF_STMT(BPF_LD+BPF_MEM, 1) + +#define JGE64(lo, hi, jt) \ + /* if (hi > arg.hi) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (hi), 4, 0), \ + /* if (hi != arg.hi) goto NOMATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \ + BPF_STMT(BPF_LD+BPF_MEM, 0), \ + /* if (lo >= arg.lo) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (lo), 0, 2), \ + BPF_STMT(BPF_LD+BPF_MEM, 1), \ + jt, \ + BPF_STMT(BPF_LD+BPF_MEM, 1) + +#define JGT64(lo, hi, jt) \ + /* if (hi > arg.hi) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (hi), 4, 0), \ + /* if (hi != arg.hi) goto NOMATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \ + BPF_STMT(BPF_LD+BPF_MEM, 0), \ + /* if (lo > arg.lo) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (lo), 0, 2), \ + BPF_STMT(BPF_LD+BPF_MEM, 1), \ + jt, \ + BPF_STMT(BPF_LD+BPF_MEM, 1) + +#define JLE64(lo, hi, jt) \ + /* if (hi < arg.hi) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (hi), 0, 4), \ + /* if (hi != arg.hi) goto NOMATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \ + BPF_STMT(BPF_LD+BPF_MEM, 0), \ + /* if (lo <= arg.lo) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JGT+BPF_K, (lo), 2, 0), \ + BPF_STMT(BPF_LD+BPF_MEM, 1), \ + jt, \ + BPF_STMT(BPF_LD+BPF_MEM, 1) + +#define JLT64(lo, hi, jt) \ + /* if (hi < arg.hi) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (hi), 0, 4), \ + /* if (hi != arg.hi) goto NOMATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (hi), 0, 5), \ + BPF_STMT(BPF_LD+BPF_MEM, 0), \ + /* if (lo < arg.lo) goto MATCH; */ \ + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, (lo), 2, 0), \ + BPF_STMT(BPF_LD+BPF_MEM, 1), \ + jt, \ + BPF_STMT(BPF_LD+BPF_MEM, 1) + +#define LOAD_SYSCALL_NR \ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \ + offsetof(struct seccomp_data, nr)) + +#define LOAD_ARCH \ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \ + offsetof(struct seccomp_data, arch)) + +#ifdef __cplusplus +} +#endif + + +#endif // SANDBOXED_API_SANDBOX2_UTIL_BPF_HELPER_H_ diff --git a/sandboxed_api/sandbox2/util/file_helpers.cc b/sandboxed_api/sandbox2/util/file_helpers.cc new file mode 100644 index 0000000..fcbd8ef --- /dev/null +++ b/sandboxed_api/sandbox2/util/file_helpers.cc @@ -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. + +#include "sandboxed_api/sandbox2/util/file_helpers.h" + +#include +#include + +#include "absl/strings/str_cat.h" + +namespace sandbox2 { +namespace file { + +const Options& Defaults() { + static auto* instance = new Options{}; + return *instance; +} + +sapi::Status GetContents(absl::string_view path, std::string* output, + const file::Options& options) { + std::ifstream in_stream{std::string(path), std::ios_base::binary}; + std::ostringstream out_stream; + out_stream << in_stream.rdbuf(); + if (!in_stream || !out_stream) { + return sapi::Status{sapi::StatusCode::kUnknown, + absl::StrCat("Error during read: ", path)}; + } + *output = out_stream.str(); + return sapi::OkStatus(); +} + +sapi::Status SetContents(absl::string_view path, absl::string_view content, + const file::Options& options) { + std::ofstream out_stream{std::string(path), + std::ios_base::trunc | std::ios_base::binary}; + if (!out_stream) { + return sapi::Status{sapi::StatusCode::kUnknown, + absl::StrCat("Failed to open file: ", path)}; + } + out_stream.write(content.data(), content.size()); + if (!out_stream) { + return sapi::Status{sapi::StatusCode::kUnknown, + absl::StrCat("Error during write: ", path)}; + } + return sapi::OkStatus(); +} + +} // namespace file +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/file_helpers.h b/sandboxed_api/sandbox2/util/file_helpers.h new file mode 100644 index 0000000..8719b57 --- /dev/null +++ b/sandboxed_api/sandbox2/util/file_helpers.h @@ -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. + +#ifndef SANDBOXED_API_SANDBOX2_UTIL_FILE_HELPERS_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_FILE_HELPERS_H_ + +#include + +#include "absl/strings/string_view.h" +#include "sandboxed_api/util/status.h" + +namespace sandbox2 { +namespace file { + +// Empty Options struct for compatibility with Google File. +struct Options {}; + +// Default constructed Options struct for compatiblity with Google File. +const Options& Defaults(); + +sapi::Status GetContents(absl::string_view path, std::string* output, + const file::Options& options); + +sapi::Status SetContents(absl::string_view path, absl::string_view content, + const file::Options& options); + +} // namespace file +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UTIL_FILE_HELPERS_H_ diff --git a/sandboxed_api/sandbox2/util/file_helpers_test.cc b/sandboxed_api/sandbox2/util/file_helpers_test.cc new file mode 100644 index 0000000..f25d36d --- /dev/null +++ b/sandboxed_api/sandbox2/util/file_helpers_test.cc @@ -0,0 +1,40 @@ +// 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/util/file_helpers.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/util/status_matchers.h" + +using ::sapi::IsOk; +using ::testing::Not; + +namespace sandbox2 { + +TEST(FileHelpersTest, TestGetNonExistent) { + std::string output; + EXPECT_THAT(file::GetContents("/not/there", &output, file::Defaults()), + Not(IsOk())); +} + +TEST(FileHelpersTest, TestGetKnownFile) { + std::string output; + EXPECT_THAT(file::GetContents("/proc/self/status", &output, file::Defaults()), + IsOk()); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/fileops.cc b/sandboxed_api/sandbox2/util/fileops.cc new file mode 100644 index 0000000..8aea708 --- /dev/null +++ b/sandboxed_api/sandbox2/util/fileops.cc @@ -0,0 +1,296 @@ +// 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/util/fileops.h" + +#include // DIR +#include // PATH_MAX +#include // stat64 +#include + +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/strip.h" +#include "sandboxed_api/sandbox2/util/strerror.h" + +namespace sandbox2 { +namespace file_util { +namespace fileops { + +FDCloser::~FDCloser() { Close(); } + +bool FDCloser::Close() { + int fd = Release(); + if (fd == -1) { + return false; + } + return close(fd) == 0 || errno == EINTR; +} + +int FDCloser::Release() { + int ret = fd_; + fd_ = -1; + return ret; +} + +bool GetCWD(std::string* result) { + // Calling getcwd() with a nullptr buffer is a commonly implemented extension. + std::unique_ptr cwd{getcwd(nullptr, 0), + [](char* p) { free(p); }}; + if (!cwd) { + return false; + } + *result = cwd.get(); + return true; +} + +// Makes a path absolute with respect to base. Returns true on success. Result +// may be an alias of base or filename. +bool MakeAbsolute(const std::string& filename, const std::string& base, std::string* result) { + if (filename.empty()) { + return false; + } + if (filename[0] == '/') { + if (result != &filename) { + *result = filename; + } + return true; + } + + std::string actual_base = base; + if (actual_base.empty() && !GetCWD(&actual_base)) { + return false; + } + + actual_base = std::string(absl::StripSuffix(actual_base, "/")); + + if (filename == ".") { + if (actual_base.empty()) { + *result = "/"; + } else { + *result = actual_base; + } + } else { + *result = actual_base + "/" + filename; + } + return true; +} + +std::string MakeAbsolute(const std::string& filename, const std::string& base) { + std::string result; + return !MakeAbsolute(filename, base, &result) ? "" : result; +} + +bool RemoveLastPathComponent(const std::string& file, std::string* output) { + // Point idx at the last non-slash in the std::string. This should mark the last + // character of the base name. + auto idx = file.find_last_not_of('/'); + // If no non-slash is found, we have all slashes or an empty std::string. Return + // the appropriate value and false to indicate there was no path component to + // remove. + if (idx == std::string::npos) { + if (file.empty()) { + output->clear(); + } else { + *output = "/"; + } + return false; + } + + // Otherwise, we have to trim the last path component. Find where it begins. + // Point idx at the last slash before the base name. + idx = file.find_last_of('/', idx); + // If we don't find a slash, then we have something of the form "file/*", so + // just return the empty std::string. + if (idx == std::string::npos) { + output->clear(); + } else { + // Then find the last character that isn't a slash, in case the slash is + // repeated. + // Point idx at the character at the last character of the path component + // that precedes the base name. I.e. if you have /foo/bar, idx will point + // at the last "o" in foo. We remove everything after this index. + idx = file.find_last_not_of('/', idx); + // If none is found, then set idx to 0 so the below code will leave the + // first slash. + if (idx == std::string::npos) idx = 0; + // This is an optimization to prevent a copy if output and file are + // aliased. + if (&file == output) { + output->erase(idx + 1, std::string::npos); + } else { + output->assign(file, 0, idx + 1); + } + } + return true; +} + +std::string ReadLink(const std::string& filename) { + std::string result(PATH_MAX, '\0'); + const auto size = readlink(filename.c_str(), &result[0], PATH_MAX); + if (size < 0) { + return ""; + } + result.resize(size); + return result; +} + +bool ReadLinkAbsolute(const std::string& filename, std::string* result) { + std::string base_dir; + + // Do this first. Otherwise, if &filename == result, we won't be able to find + // it after the ReadLink call. + RemoveLastPathComponent(filename, &base_dir); + + std::string link = ReadLink(filename); + if (link.empty()) { + return false; + } + *result = std::move(link); + + // Need two calls in case filename itself is relative. + return MakeAbsolute(MakeAbsolute(*result, base_dir), "", result); +} + +std::string Basename(absl::string_view path) { + const auto last_slash = path.find_last_of('/'); + return std::string(last_slash == std::string::npos + ? path + : absl::ClippedSubstr(path, last_slash + 1)); +} + +std::string StripBasename(absl::string_view path) { + const auto last_slash = path.find_last_of('/'); + if (last_slash == std::string::npos) { + return ""; + } + if (last_slash == 0) { + return "/"; + } + return std::string(path.substr(0, last_slash)); +} + +bool Exists(const std::string& filename, bool fully_resolve) { + struct stat64 st; + return (fully_resolve ? stat64(filename.c_str(), &st) + : lstat64(filename.c_str(), &st)) != -1; +} + +bool ListDirectoryEntries(const std::string& directory, std::vector* entries, + std::string* error) { + errno = 0; + std::unique_ptr dir{opendir(directory.c_str()), + [](DIR* d) { closedir(d); }}; + if (!dir) { + *error = absl::StrCat("opendir(", directory, "): ", StrError(errno)); + return false; + } + + struct dirent64* entry{}; + errno = 0; + int bufferlen = + std::max(offsetof(struct dirent64, d_name) + + pathconf(directory.c_str(), _PC_NAME_MAX) + 1, + sizeof(struct dirent64)); + std::unique_ptr dirent_buffer{ + static_cast(malloc(bufferlen)), + [](struct dirent64* p) { free(p); }}; + while (readdir64_r(dir.get(), dirent_buffer.get(), &entry) == 0 && entry) { + const std::string name(entry->d_name); + if (name != "." && name != "..") { + entries->push_back(name); + } + } + if (errno != 0) { + *error = absl::StrCat("readdir(", directory, "): ", StrError(errno)); + return false; + } + return true; +} + +bool DeleteRecursively(const std::string& filename) { + std::vector to_delete; + to_delete.push_back(filename); + + while (!to_delete.empty()) { + const std::string delfile = to_delete.back(); + + struct stat64 st; + if (lstat64(delfile.c_str(), &st) == -1) { + if (errno == ENOENT) { + // Most likely the first file. Either that or someone is deleting the + // files out from under us. + to_delete.pop_back(); + continue; + } + return false; + } + + if (S_ISDIR(st.st_mode)) { + if (rmdir(delfile.c_str()) != 0 && errno != ENOENT) { + if (errno == ENOTEMPTY) { + std::string error; + std::vector entries; + if (!ListDirectoryEntries(delfile, &entries, &error)) { + return false; + } + for (const auto& entry : entries) { + to_delete.push_back(delfile + "/" + entry); + } + } else { + return false; + } + } else { + to_delete.pop_back(); + } + } else { + if (unlink(delfile.c_str()) != 0 && errno != ENOENT) { + return false; + } + to_delete.pop_back(); + } + } + return true; +} + +bool CopyFile(const std::string& old_path, const std::string& new_path, int new_mode) { + { + std::ifstream input(old_path, std::ios_base::binary); + std::ofstream output(new_path, + std::ios_base::trunc | std::ios_base::binary); + output << input.rdbuf(); + if (!input || !output) { + return false; + } + } + return chmod(new_path.c_str(), new_mode) == 0; +} + +bool WriteToFD(int fd, const char* data, size_t size) { + while (size > 0) { + ssize_t result = TEMP_FAILURE_RETRY(write(fd, data, size)); + if (result <= 0) { + return false; + } + size -= result; + data += result; + } + return true; +} + +} // namespace fileops +} // namespace file_util +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/fileops.h b/sandboxed_api/sandbox2/util/fileops.h new file mode 100644 index 0000000..850cb28 --- /dev/null +++ b/sandboxed_api/sandbox2/util/fileops.h @@ -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. + +#ifndef SANDBOXED_API_SANDBOX2_UTIL_FILEOPS_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_FILEOPS_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +namespace sandbox2 { +namespace file_util { +namespace fileops { + +// RAII helper class to automatically close file descriptors. +class FDCloser { + public: + explicit FDCloser(int fd) : fd_{fd} {} + ~FDCloser(); + + int get() const { return fd_; } + bool Close(); + int Release(); + + private: + int fd_; +}; + +// Returns the target of a symlink. Returns an empty string on failure. +std::string ReadLink(const std::string& filename); + +// Reads the absolute path to the symlink target into result. Returns true on +// success. result and filename may be aliased. +bool ReadLinkAbsolute(const std::string& filename, std::string* result); + +// Removes the last path component. Returns false if there was no path +// component (path is / or ""). If this function returns false, *output will be +// equal to "/" or "" if the file is absolute or relative, respectively. output +// and file may refer to the same string. +bool RemoveLastPathComponent(const std::string& file, std::string* output); + +// Returns a file's basename, i.e. +// If the input path has a trailing slash, the basename is assumed to be +// empty, e.g. StripBasename("/hello/") == "/hello". +// Does no path cleanups; the result is always a prefix/ suffix of the +// passed string. +std::string Basename(absl::string_view path); + +// Like above, but returns a file's directory name. +std::string StripBasename(absl::string_view path); + +// Tests whether filename exists. If fully_resolve is true, then all symlinks +// are resolved to verify the target exists. Otherwise, this function +// verifies only that the file exists. It may still be a symlink with a +// missing target. +bool Exists(const std::string& filename, bool fully_resolve); + +// Reads a directory and fills entries with all the files in that directory. +// On error, false is returned and error is set to a description of the +// error. The filenames in entries are just the basenames of the +// files found. +bool ListDirectoryEntries(const std::string& directory, std::vector* entries, + std::string* error); + +// Deletes the specified file or directory, including any sub-directories. +bool DeleteRecursively(const std::string& filename); + +// Copies a file from one location to another. The file will be overwritten if +// it already exists. If it does not exist, its mode will be new_mode. Returns +// true on success. On failure, a partial copy of the file may remain. +bool CopyFile(const std::string& old_path, const std::string& new_path, int new_mode); + +// Makes filename absolute with respect to base. Returns an empty string on +// failure. +std::string MakeAbsolute(const std::string& filename, const std::string& base); + +// Writes data to a file descriptor. The file descriptor should be blocking. +// Returns true on success. +bool WriteToFD(int fd, const char* data, size_t size); + +} // namespace fileops +} // namespace file_util +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UTIL_FILEOPS_H_ diff --git a/sandboxed_api/sandbox2/util/fileops_test.cc b/sandboxed_api/sandbox2/util/fileops_test.cc new file mode 100644 index 0000000..0c741a7 --- /dev/null +++ b/sandboxed_api/sandbox2/util/fileops_test.cc @@ -0,0 +1,433 @@ +// 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/util/fileops.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/file_helpers.h" +#include "sandboxed_api/util/status_matchers.h" + +using sapi::IsOk; +using testing::Eq; +using testing::IsEmpty; +using testing::IsFalse; +using testing::IsTrue; +using testing::Ne; +using testing::SizeIs; +using testing::StrEq; + +namespace sandbox2 { +namespace file_util { + +// Forward declare functions that are only used in fileops.cc. +namespace fileops { +bool GetCWD(std::string* result); +bool RemoveLastPathComponent(const std::string& file, std::string* output); +} // namespace fileops + +namespace { + +class FileOpsTest : public testing::Test { + protected: + static void SetUpTestCase() { + ASSERT_THAT(chdir(GetTestTempPath().c_str()), Eq(0)); + } +}; + +TEST_F(FileOpsTest, GetCWDTest) { + std::string result; + ASSERT_THAT(fileops::GetCWD(&result), IsTrue()); + EXPECT_THAT(result, StrEq(GetTestTempPath())); +} + +TEST_F(FileOpsTest, MakeAbsoluteTest) { + const auto tmp_dir = GetTestTempPath(); + ASSERT_THAT(chdir(tmp_dir.c_str()), Eq(0)); + EXPECT_THAT(fileops::MakeAbsolute("", ""), StrEq("")); + EXPECT_THAT(fileops::MakeAbsolute(".", ""), StrEq(tmp_dir)); + EXPECT_THAT(fileops::MakeAbsolute(".", tmp_dir), StrEq(tmp_dir)); + EXPECT_THAT(fileops::MakeAbsolute(".", "/"), StrEq("/")); + EXPECT_THAT(fileops::MakeAbsolute("/", tmp_dir), StrEq("/")); + EXPECT_THAT(fileops::MakeAbsolute("/", "/"), StrEq("/")); + EXPECT_THAT(fileops::MakeAbsolute("/", ""), StrEq("/")); + EXPECT_THAT(fileops::MakeAbsolute("/foo/bar", ""), StrEq("/foo/bar")); + EXPECT_THAT(fileops::MakeAbsolute("foo/bar", ""), + StrEq(absl::StrCat(tmp_dir, "/foo/bar"))); + EXPECT_THAT(fileops::MakeAbsolute("foo/bar", tmp_dir), + StrEq(absl::StrCat(tmp_dir, "/foo/bar"))); + EXPECT_THAT(fileops::MakeAbsolute("foo/bar", tmp_dir + "/"), + StrEq(absl::StrCat(tmp_dir, "/foo/bar"))); +} + +TEST_F(FileOpsTest, ExistsTest) { + ASSERT_THAT(file::SetContents("exists_test", "", file::Defaults()), IsOk()); + EXPECT_THAT(fileops::Exists("exists_test", false), IsTrue()); + EXPECT_THAT(fileops::Exists("exists_test", true), IsTrue()); + + ASSERT_THAT(symlink("exists_test", "exists_test_link"), Eq(0)); + EXPECT_THAT(fileops::Exists("exists_test_link", false), IsTrue()); + EXPECT_THAT(fileops::Exists("exists_test_link", true), IsTrue()); + + ASSERT_THAT(unlink("exists_test"), Eq(0)); + EXPECT_THAT(fileops::Exists("exists_test_link", false), IsTrue()); + EXPECT_THAT(fileops::Exists("exists_test_link", true), IsFalse()); + + ASSERT_THAT(unlink("exists_test_link"), Eq(0)); + EXPECT_THAT(fileops::Exists("exists_test_link", false), IsFalse()); + EXPECT_THAT(fileops::Exists("exists_test_link", true), IsFalse()); +} + +TEST_F(FileOpsTest, ReadLinkTest) { + EXPECT_THAT(fileops::ReadLink("readlink_not_there"), StrEq("")); + EXPECT_THAT(errno, Eq(ENOENT)); + + ASSERT_THAT(file::SetContents("readlink_file", "", file::Defaults()), IsOk()); + EXPECT_THAT(fileops::ReadLink("readlink_file"), StrEq("")); + unlink("readlink_file"); + + ASSERT_THAT(symlink("..", "readlink_dotdot"), Eq(0)); + EXPECT_THAT(fileops::ReadLink("readlink_dotdot"), StrEq("..")); + unlink("readlink_dotdot"); + + ASSERT_THAT(symlink("../", "readlink_dotdotslash"), 0); + EXPECT_THAT(fileops::ReadLink("readlink_dotdotslash"), "../"); + unlink("readlink_dotdotslash"); + + ASSERT_THAT(symlink("/", "readlink_slash"), 0); + EXPECT_THAT(fileops::ReadLink("readlink_slash"), "/"); + unlink("readlink_slash"); + + const std::string very_long_name(PATH_MAX - 1, 'f'); + ASSERT_THAT(symlink(very_long_name.c_str(), "readlink_long"), Eq(0)); + EXPECT_THAT(fileops::ReadLink("readlink_long"), StrEq(very_long_name)); + unlink("readlink_long"); +} + +TEST_F(FileOpsTest, ListDirectoryEntriesFailTest) { + std::vector files; + std::string error; + + EXPECT_THAT(fileops::ListDirectoryEntries("new_dir", &files, &error), + IsFalse()); + EXPECT_THAT(files, IsEmpty()); + EXPECT_THAT(error, StrEq("opendir(new_dir): No such file or directory")); +} + +TEST_F(FileOpsTest, ListDirectoryEntriesEmptyTest) { + std::vector files; + std::string error; + + ASSERT_THAT(mkdir("new_dir", 0700), Eq(0)); + + EXPECT_THAT(fileops::ListDirectoryEntries("new_dir", &files, &error), + IsTrue()); + EXPECT_THAT(files, IsEmpty()); + + rmdir("new_dir"); +} + +TEST_F(FileOpsTest, ListDirectoryEntriesOneFileTest) { + ASSERT_THAT(mkdir("new_dir", 0700), Eq(0)); + ASSERT_THAT(file::SetContents("new_dir/first", "", file::Defaults()), IsOk()); + + std::vector files; + std::string error; + EXPECT_THAT(fileops::ListDirectoryEntries("new_dir", &files, &error), + IsTrue()); + + unlink("new_dir/first"); + rmdir("new_dir"); + + ASSERT_THAT(files, SizeIs(1)); + EXPECT_THAT(files[0], "first"); +} + +TEST_F(FileOpsTest, ListDirectoryEntriesTest) { + ASSERT_THAT(mkdir("new_dir", 0700), Eq(0)); + constexpr int kNumFiles = 10; + for (int i = 0; i < kNumFiles; ++i) { + ASSERT_THAT(file::SetContents(absl::StrCat("new_dir/file", i), "", + file::Defaults()), + IsOk()); + } + + std::vector files; + std::string error; + EXPECT_THAT(fileops::ListDirectoryEntries("new_dir", &files, &error), + IsTrue()); + + fileops::DeleteRecursively("new_dir"); + + ASSERT_THAT(files, SizeIs(kNumFiles)); + std::sort(files.begin(), files.end()); + for (int i = 0; i < kNumFiles; ++i) { + EXPECT_THAT(files[i], StrEq(absl::StrCat("file", i))); + } +} + +TEST_F(FileOpsTest, RemoveLastPathComponentTest) { + std::string result; + + EXPECT_THAT(fileops::RemoveLastPathComponent("/", &result), IsFalse()); + EXPECT_THAT(result, StrEq("/")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("///", &result), IsFalse()); + EXPECT_THAT(result, StrEq("/")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home", &result), IsTrue()); + EXPECT_THAT(result, StrEq("/")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home/", &result), IsTrue()); + EXPECT_THAT(result, StrEq("/")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home///", &result), IsTrue()); + EXPECT_THAT(result, StrEq("/")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home///", &result), IsTrue()); + EXPECT_THAT(result, StrEq("/")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("///home///", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home/someone", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/home")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home///someone", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/home")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home///someone/", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/home")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home///someone//", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/home")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home/someone/file", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/home/someone")); + + EXPECT_THAT( + fileops::RemoveLastPathComponent("/home/someone////file", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/home/someone")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home///someone/file", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/home///someone")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/home/someone/file", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("/home/someone")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("no_root", &result), IsTrue()); + EXPECT_THAT(result, StrEq("")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("no_root/", &result), IsTrue()); + EXPECT_THAT(result, StrEq("")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("no_root///", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("/file", &result), IsTrue()); + EXPECT_THAT(result, "/"); + + EXPECT_THAT(fileops::RemoveLastPathComponent("no_root/file", &result), + IsTrue()); + EXPECT_THAT(result, StrEq("no_root")); + + result = "no_root"; + EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); + EXPECT_THAT(result, StrEq("")); + + result = "no_root/"; + EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); + EXPECT_THAT(result, StrEq("")); + + result = "no_root///"; + EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); + EXPECT_THAT(result, StrEq("")); + + result = "/file"; + EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); + EXPECT_THAT(result, StrEq("/")); + + result = "no_root/file"; + EXPECT_THAT(fileops::RemoveLastPathComponent(result, &result), IsTrue()); + EXPECT_THAT(result, StrEq("no_root")); + + EXPECT_THAT(fileops::RemoveLastPathComponent("", &result), IsFalse()); + EXPECT_THAT(result, StrEq("")); +} + +TEST_F(FileOpsTest, TestBasename) { + EXPECT_THAT(fileops::Basename(""), StrEq("")); + EXPECT_THAT(fileops::Basename("/"), StrEq("")); + EXPECT_THAT(fileops::Basename("//"), StrEq("")); + EXPECT_THAT(fileops::Basename("/hello/"), StrEq("")); + EXPECT_THAT(fileops::Basename("//hello"), StrEq("hello")); + EXPECT_THAT(fileops::Basename("/hello/world"), StrEq("world")); + EXPECT_THAT(fileops::Basename("/hello, world"), StrEq("hello, world")); +} + +TEST_F(FileOpsTest, TestStripBasename) { + EXPECT_THAT(fileops::StripBasename(""), StrEq("")); + EXPECT_THAT(fileops::StripBasename("/"), StrEq("/")); + EXPECT_THAT(fileops::StripBasename("//"), StrEq("/")); + EXPECT_THAT(fileops::StripBasename("/hello"), StrEq("/")); + EXPECT_THAT(fileops::StripBasename("//hello"), StrEq("/")); + EXPECT_THAT(fileops::StripBasename("/hello/"), StrEq("/hello")); + EXPECT_THAT(fileops::StripBasename("/hello//"), StrEq("/hello/")); + EXPECT_THAT(fileops::StripBasename("/hello/world"), StrEq("/hello")); + EXPECT_THAT(fileops::StripBasename("/hello, world"), StrEq("/")); +} + +void SetupDirectory() { + ASSERT_THAT(mkdir("foo", 0755), Eq(0)); + ASSERT_THAT(mkdir("foo/bar", 0755), Eq(0)); + ASSERT_THAT(mkdir("foo/baz", 0755), Eq(0)); + ASSERT_THAT(file::SetContents("foo/quux", "", file::Defaults()), IsOk()); + ASSERT_THAT(chmod("foo/quux", 0644), Eq(0)); + + ASSERT_THAT(file::SetContents("foo/bar/foo", "", file::Defaults()), IsOk()); + ASSERT_THAT(chmod("foo/bar/foo", 0644), Eq(0)); + ASSERT_THAT(file::SetContents("foo/bar/bar", "", file::Defaults()), IsOk()); + ASSERT_THAT(chmod("foo/bar/bar", 0644), Eq(0)); + + ASSERT_THAT(mkdir("foo/bar/baz", 0755), Eq(0)); + ASSERT_THAT(file::SetContents("foo/bar/baz/foo", "", file::Defaults()), + IsOk()); + ASSERT_THAT(chmod("foo/bar/baz/foo", 0644), Eq(0)); +} + +TEST_F(FileOpsTest, DeleteRecursivelyTest) { + EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); + EXPECT_THAT(fileops::DeleteRecursively("/not_there"), IsTrue()); + + // Can't stat file + SetupDirectory(); + ASSERT_THAT(chmod("foo/bar/baz", 0000), Eq(0)); + EXPECT_THAT(fileops::DeleteRecursively("foo/bar/baz/quux"), IsFalse()); + EXPECT_THAT(errno, Eq(EACCES)); + ASSERT_THAT(chmod("foo/bar/baz", 0755), Eq(0)); + + EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); + struct stat64 st; + EXPECT_THAT(lstat64("foo", &st), Ne(0)); + + // Can't list subdirectory + SetupDirectory(); + ASSERT_THAT(chmod("foo/bar/baz", 0000), Eq(0)); + EXPECT_THAT(fileops::DeleteRecursively("foo"), IsFalse()); + EXPECT_THAT(errno, Eq(EACCES)); + ASSERT_THAT(chmod("foo/bar/baz", 0755), Eq(0)); + + EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); + + // Can't delete file + SetupDirectory(); + ASSERT_THAT(chmod("foo/bar/baz", 0500), Eq(0)); + EXPECT_THAT(fileops::DeleteRecursively("foo"), IsFalse()); + EXPECT_THAT(errno, Eq(EACCES)); + ASSERT_THAT(chmod("foo/bar/baz", 0755), Eq(0)); + + EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); + + // Can't delete directory + SetupDirectory(); + ASSERT_THAT(fileops::DeleteRecursively("foo/bar/baz/foo"), IsTrue()); + ASSERT_THAT(chmod("foo/bar", 0500), Eq(0)); + EXPECT_THAT(fileops::DeleteRecursively("foo/bar/baz"), IsFalse()); + EXPECT_THAT(errno, Eq(EACCES)); + ASSERT_THAT(chmod("foo/bar", 0755), Eq(0)); + + EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); +} + +TEST_F(FileOpsTest, ReadLinkAbsoluteTest) { + const auto tmp_dir = GetTestTempPath(); + ASSERT_THAT(chdir(tmp_dir.c_str()), Eq(0)); + + EXPECT_THAT(fileops::DeleteRecursively("foo"), IsTrue()); + ASSERT_THAT(symlink("rel/path", "foo"), Eq(0)); + + const std::string expected_path = absl::StrCat(tmp_dir, "/rel/path"); + const std::string expected_path2 = absl::StrCat(tmp_dir, "/./rel/path"); + std::string result; + EXPECT_THAT(fileops::ReadLinkAbsolute("foo", &result), IsTrue()); + EXPECT_THAT(result, StrEq(expected_path)); + EXPECT_THAT(fileops::ReadLinkAbsolute("./foo", &result), IsTrue()); + EXPECT_THAT(result, StrEq(expected_path2)); + EXPECT_THAT(fileops::ReadLinkAbsolute(absl::StrCat(tmp_dir, "/foo"), &result), + IsTrue()); + EXPECT_THAT(result, StrEq(expected_path)); + + result.clear(); + EXPECT_THAT(fileops::ReadLinkAbsolute("/not_there", &result), IsFalse()); + EXPECT_THAT(result, IsEmpty()); +} + +TEST_F(FileOpsTest, CopyFileTest) { + const auto tmp_dir = GetTestTempPath(); + // Non-existent source + EXPECT_THAT( + fileops::CopyFile("/not/there", absl::StrCat(tmp_dir, "/out"), 0777), + IsFalse()); + + // Unwritable target + EXPECT_THAT(fileops::CopyFile("/proc/self/exe", tmp_dir, 0777), IsFalse()); + + EXPECT_THAT(file::SetContents(absl::StrCat(tmp_dir, "/test"), "test\n", + file::Defaults()), + IsOk()); + EXPECT_THAT(fileops::CopyFile(absl::StrCat(tmp_dir, "/test"), + absl::StrCat(tmp_dir, "/test2"), 0666), + IsTrue()); + + std::string text; + EXPECT_THAT(file::GetContents(absl::StrCat(tmp_dir, "/test2"), &text, + file::Defaults()), + IsOk()); + + EXPECT_THAT(text, StrEq("test\n")); + + unlink((absl::StrCat(tmp_dir, "/test")).c_str()); + unlink((absl::StrCat(tmp_dir, "/test2")).c_str()); +} + +} // namespace +} // namespace file_util +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/maps_parser.cc b/sandboxed_api/sandbox2/util/maps_parser.cc new file mode 100644 index 0000000..a8cdfb3 --- /dev/null +++ b/sandboxed_api/sandbox2/util/maps_parser.cc @@ -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. + +#include "sandboxed_api/sandbox2/util/maps_parser.h" +#include "absl/strings/str_split.h" +#include "sandboxed_api/util/canonical_errors.h" + +namespace sandbox2 { + +::sapi::StatusOr> ParseProcMaps(const std::string& contents) { + // Note: The format std::string + // https://github.com/torvalds/linux/blob/v4.14/fs/proc/task_mmu.c#L289 + // changed to a non-format std::string implementation + // (show_vma_header_prefix()). + static constexpr char kFormatString[] = + "%lx-%lx %c%c%c%c %lx %x:%x %lu %1023s"; + static constexpr size_t kFilepathLength = 1023; + + std::vector lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); + std::vector entries; + for (const auto& line : lines) { + MapsEntry entry{}; + char r, w, x, s; + entry.path.resize(kFilepathLength + 1, '\0'); + int n_matches = sscanf( + line.c_str(), kFormatString, &entry.start, &entry.end, &r, &w, &x, &s, + &entry.pgoff, &entry.major, &entry.minor, &entry.inode, &entry.path[0]); + + // Some lines do not have a filename. + if (n_matches == 10) { + entry.path.clear(); + } else if (n_matches == 11) { + entry.path.resize(strlen(entry.path.c_str())); + } else { + return ::sapi::FailedPreconditionError("Invalid format"); + } + entry.is_readable = r == 'r'; + entry.is_writable = w == 'w'; + entry.is_executable = x == 'x'; + entry.is_shared = s == 's'; + entries.push_back(entry); + } + return entries; +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/maps_parser.h b/sandboxed_api/sandbox2/util/maps_parser.h new file mode 100644 index 0000000..da71d4b --- /dev/null +++ b/sandboxed_api/sandbox2/util/maps_parser.h @@ -0,0 +1,44 @@ +// 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_UTIL_MAPS_PARSER_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_MAPS_PARSER_H_ + +#include +#include + +#include "sandboxed_api/util/status.h" +#include "sandboxed_api/util/statusor.h" + +namespace sandbox2 { + +struct MapsEntry { + uint64_t start; + uint64_t end; + bool is_readable; + bool is_writable; + bool is_executable; + bool is_shared; + uint64_t pgoff; + int major; + int minor; + uint64_t inode; + std::string path; +}; + +::sapi::StatusOr> ParseProcMaps(const std::string& contents); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UTIL_MAPS_PARSER_H_ diff --git a/sandboxed_api/sandbox2/util/maps_parser_test.cc b/sandboxed_api/sandbox2/util/maps_parser_test.cc new file mode 100644 index 0000000..33fe73a --- /dev/null +++ b/sandboxed_api/sandbox2/util/maps_parser_test.cc @@ -0,0 +1,89 @@ +// 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/util/maps_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/util/status_matchers.h" + +namespace sandbox2 { +namespace { + +using ::sapi::IsOk; +using ::testing::Eq; +using ::testing::Not; +using ::testing::Test; + +TEST(MapsParserTest, ParsesValidFileCorrectly) { + static constexpr char kValidMapsFile[] = R"ValidMapsFile( +555555554000-55555555c000 r-xp 00000000 fd:01 3277961 /bin/cat +55555575b000-55555575c000 r--p 00007000 fd:01 3277961 /bin/cat +55555575c000-55555575d000 rw-p 00008000 fd:01 3277961 /bin/cat +55555575d000-55555577e000 rw-p 00000000 00:00 0 [heap] +7ffff7a3a000-7ffff7bcf000 r-xp 00000000 fd:01 916748 /lib/x86_64-linux-gnu/libc-2.24.so +7ffff7bcf000-7ffff7dcf000 ---p 00195000 fd:01 916748 /lib/x86_64-linux-gnu/libc-2.24.so +7ffff7dcf000-7ffff7dd3000 r--p 00195000 fd:01 916748 /lib/x86_64-linux-gnu/libc-2.24.so +7ffff7dd3000-7ffff7dd5000 rw-p 00199000 fd:01 916748 /lib/x86_64-linux-gnu/libc-2.24.so +7ffff7dd5000-7ffff7dd9000 rw-p 00000000 00:00 0 +7ffff7dd9000-7ffff7dfc000 r-xp 00000000 fd:01 915984 /lib/x86_64-linux-gnu/ld-2.24.so +7ffff7e2b000-7ffff7e7c000 r--p 00000000 fd:01 917362 /usr/lib/locale/aa_DJ.utf8/LC_CTYPE +7ffff7e7c000-7ffff7fac000 r--p 00000000 fd:01 917355 /usr/lib/locale/aa_DJ.utf8/LC_COLLATE +7ffff7fac000-7ffff7fae000 rw-p 00000000 00:00 0 +7ffff7fc1000-7ffff7fe3000 rw-p 00000000 00:00 0 +7ffff7fe3000-7ffff7fe4000 r--p 00000000 fd:01 920638 /usr/lib/locale/aa_ET/LC_NUMERIC +7ffff7fe4000-7ffff7fe5000 r--p 00000000 fd:01 932780 /usr/lib/locale/en_US.utf8/LC_TIME +7ffff7fe5000-7ffff7fe6000 r--p 00000000 fd:01 932409 /usr/lib/locale/chr_US/LC_MONETARY +7ffff7fe6000-7ffff7fe7000 r--p 00000000 fd:01 932625 /usr/lib/locale/en_AG/LC_MESSAGES/SYS_LC_MESSAGES +7ffff7fe7000-7ffff7fe8000 r--p 00000000 fd:01 932411 /usr/lib/locale/chr_US/LC_PAPER +7ffff7fe8000-7ffff7fe9000 r--p 00000000 fd:01 932410 /usr/lib/locale/chr_US/LC_NAME +7ffff7fe9000-7ffff7fea000 r--p 00000000 fd:01 932778 /usr/lib/locale/en_US.utf8/LC_ADDRESS +7ffff7fea000-7ffff7feb000 r--p 00000000 fd:01 932412 /usr/lib/locale/chr_US/LC_TELEPHONE +7ffff7feb000-7ffff7fec000 r--p 00000000 fd:01 932407 /usr/lib/locale/chr_US/LC_MEASUREMENT +7ffff7fec000-7ffff7ff3000 r--s 00000000 fd:01 1179918 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache +7ffff7ff3000-7ffff7ff4000 r--p 00000000 fd:01 932779 /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION +7ffff7ff4000-7ffff7ff7000 rw-p 00000000 00:00 0 +7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar] +7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] +7ffff7ffc000-7ffff7ffd000 r--p 00023000 fd:01 915984 /lib/x86_64-linux-gnu/ld-2.24.so +7ffff7ffd000-7ffff7ffe000 rw-p 00024000 fd:01 915984 /lib/x86_64-linux-gnu/ld-2.24.so +7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 +7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] +)ValidMapsFile"; // NOLINT + SAPI_ASSERT_OK_AND_ASSIGN(std::vector entries, + ParseProcMaps(kValidMapsFile)); + EXPECT_THAT(entries.size(), Eq(32)); + EXPECT_THAT(entries[0].start, Eq(0x555555554000)); + EXPECT_THAT(entries[1].start, Eq(0x55555575b000)); + EXPECT_THAT(entries[1].end, Eq(0x55555575c000)); + EXPECT_THAT(entries[1].inode, Eq(3277961)); + + EXPECT_THAT(entries[0].is_executable, Eq(true)); + EXPECT_THAT(entries[1].is_executable, Eq(false)); +} + +TEST(MapsParserTest, FailsOnInvalidFile) { + static constexpr char kInvalidMapsFile[] = R"InvalidMapsFile( +555555554000-55555555c000 r-xp 00000000 fd:01 3277961 /bin/cat +55555575b000-55555575c000 r--p 00007000 fd:01 3277961 /bin/cat +55555575c000-55555575d000 rw-p 00008000 fd:01 3277961 /bin/cat +55555575d000-55555577e000 rw-p 00000000 00:00 0 [heap] +7ffff7fe4000+7ffff7fe5000 r--p 00000000 fdX01 932780 /usr/lib/locale/en_US.utf8/LC_TIME +)InvalidMapsFile"; + auto status_or = ParseProcMaps(kInvalidMapsFile); + ASSERT_THAT(status_or.status(), Not(IsOk())); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/minielf.cc b/sandboxed_api/sandbox2/util/minielf.cc new file mode 100644 index 0000000..076b6dd --- /dev/null +++ b/sandboxed_api/sandbox2/util/minielf.cc @@ -0,0 +1,529 @@ +// 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/util/minielf.h" + +#include + +#include +#include + +#include "absl/base/internal/endian.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.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/canonical_errors.h" +#include "sandboxed_api/util/status.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sandbox2 { + +constexpr int kElfHeaderSize = + sizeof(Elf64_Ehdr); // Maximum size for 64-bit binaries + +constexpr char kElfMagic[] = + "\x7F" + "ELF"; + +constexpr int kEiClassOffset = 0x04; +constexpr int kEiClass64 = 2; // 64-bit binary + +constexpr int kEiDataOffset = 0x05; +constexpr int kEiDataLittle = 1; // Little Endian +constexpr int kEiDataBig = 2; // Big Endian + +constexpr int kEiVersionOffset = 0x06; +constexpr int kEvCurrent = 1; // ELF version + +namespace { + +// NOLINTNEXTLINE +::sapi::Status CheckedFSeek(FILE* f, long offset, int whence) { + if (fseek(f, offset, whence)) { + return ::sapi::FailedPreconditionError( + absl::StrCat("Fseek on ELF failed: ", StrError(errno))); + } + return ::sapi::OkStatus(); +} + +::sapi::Status CheckedFRead(void* dst, size_t size, size_t nmemb, FILE* f) { + if (fread(dst, size, nmemb, f) == nmemb) { + return ::sapi::OkStatus(); + } + return ::sapi::FailedPreconditionError( + absl::StrCat("Reading ELF data failed: ", StrError(errno))); +} + +::sapi::Status CheckedRead(std::string* s, FILE* f) { + return CheckedFRead(&(*s)[0], 1, s->size(), f); +} + +absl::string_view ReadName(uint32_t offset, absl::string_view strtab) { + auto name = strtab.substr(offset); + return name.substr(0, name.find('\0')); +} + +} // namespace + +#define LOAD_MEMBER(data_struct, member, src) \ + Load(&(data_struct).member, \ + &src[offsetof(std::remove_reference::type, \ + member)]) + +class ElfParser { + public: + // Arbitrary cut-off values, so we can parse safely. + static constexpr int kMaxProgramHeaderEntries = 500; + static constexpr int kMaxSectionHeaderEntries = 500; + static constexpr size_t kMaxSectionSize = 50 * 1024 * 1024; + static constexpr size_t kMaxStrtabSize = 500 * 1024 * 1024; + static constexpr size_t kMaxLibPathSize = 1024; + static constexpr int kMaxSymbolEntries = 100000; + static constexpr int kMaxDynamicEntries = 10000; + static constexpr size_t kMaxInterpreterSize = 1000; + + ElfParser() = default; + ::sapi::StatusOr Parse(FILE* elf, uint32_t features); + + private: + // Endianess support functions + uint16_t Load16(const void* src) { + return elf_little_ ? absl::little_endian::Load16(src) + : absl::big_endian::Load16(src); + } + uint32_t Load32(const void* src) { + return elf_little_ ? absl::little_endian::Load32(src) + : absl::big_endian::Load32(src); + } + uint64_t Load64(const void* src) { + return elf_little_ ? absl::little_endian::Load64(src) + : absl::big_endian::Load64(src); + } + template + void Load(unsigned char (*dst)[N], const void* src) { + memcpy(dst, src, N); + } + void Load(uint8_t* dst, const void* src) { + *dst = *reinterpret_cast(src); + } + void Load(uint16_t* dst, const void* src) { *dst = Load16(src); } + void Load(uint32_t* dst, const void* src) { *dst = Load32(src); } + void Load(uint64_t* dst, const void* src) { *dst = Load64(src); } + void Load(int8_t* dst, const void* src) { + *dst = *reinterpret_cast(src); + } + void Load(int16_t* dst, const void* src) { *dst = Load16(src); } + void Load(int32_t* dst, const void* src) { *dst = Load32(src); } + void Load(int64_t* dst, const void* src) { *dst = Load64(src); } + + // Reads elf file size. + ::sapi::Status ReadFileSize(); + // Reads elf header. + ::sapi::Status ReadFileHeader(); + // Reads a single elf program header. + ::sapi::StatusOr ReadProgramHeader(absl::string_view src); + // Reads all elf program headers. + ::sapi::Status ReadProgramHeaders(); + // Reads a single elf section header. + ::sapi::StatusOr ReadSectionHeader(absl::string_view src); + // Reads all elf section headers. + ::sapi::Status ReadSectionHeaders(); + // Reads contents of an elf section. + ::sapi::StatusOr ReadSectionContents(int idx); + ::sapi::StatusOr ReadSectionContents( + const Elf64_Shdr& section_header); + // Reads all symbols from symtab section. + ::sapi::Status ReadSymbolsFromSymtab(const Elf64_Shdr& symtab); + // Reads all imported libraries from dynamic section. + ::sapi::Status ReadImportedLibrariesFromDynamic(const Elf64_Shdr& dynamic); + + ElfFile result_; + FILE* elf_ = nullptr; + size_t file_size_ = 0; + bool elf_little_ = false; + Elf64_Ehdr file_header_; + std::vector program_headers_; + std::vector section_headers_; + + int symbol_entries_read = 0; + int dynamic_entries_read = 0; +}; + +constexpr int ElfParser::kMaxProgramHeaderEntries; +constexpr int ElfParser::kMaxSectionHeaderEntries; +constexpr size_t ElfParser::kMaxSectionSize; +constexpr size_t ElfParser::kMaxStrtabSize; +constexpr size_t ElfParser::kMaxLibPathSize; +constexpr int ElfParser::kMaxSymbolEntries; +constexpr int ElfParser::kMaxDynamicEntries; +constexpr size_t ElfParser::kMaxInterpreterSize; + +::sapi::Status ElfParser::ReadFileSize() { + fseek(elf_, 0, SEEK_END); + file_size_ = ftell(elf_); + if (file_size_ < kElfHeaderSize) { + return ::sapi::FailedPreconditionError( + absl::StrCat("file too small: ", file_size_, " bytes, at least ", + kElfHeaderSize, " bytes expected")); + } + return ::sapi::OkStatus(); +} + +::sapi::Status ElfParser::ReadFileHeader() { + std::string header(kElfHeaderSize, '\0'); + SAPI_RETURN_IF_ERROR(CheckedFSeek(elf_, 0, SEEK_SET)); + SAPI_RETURN_IF_ERROR(CheckedRead(&header, elf_)); + + if (!absl::StartsWith(header, kElfMagic)) { + return ::sapi::FailedPreconditionError("magic not found, not an ELF"); + } + + if (header[kEiClassOffset] != kEiClass64) { + return ::sapi::FailedPreconditionError("invalid ELF class"); + } + const auto elf_data = header[kEiDataOffset]; + elf_little_ = elf_data == kEiDataLittle; + if (!elf_little_ && elf_data != kEiDataBig) { + return ::sapi::FailedPreconditionError("invalid endianness"); + } + + if (header[kEiVersionOffset] != kEvCurrent) { + return ::sapi::FailedPreconditionError("invalid ELF version"); + } + LOAD_MEMBER(file_header_, e_ident, header.data()); + LOAD_MEMBER(file_header_, e_type, header.data()); + LOAD_MEMBER(file_header_, e_machine, header.data()); + LOAD_MEMBER(file_header_, e_version, header.data()); + LOAD_MEMBER(file_header_, e_entry, header.data()); + LOAD_MEMBER(file_header_, e_phoff, header.data()); + LOAD_MEMBER(file_header_, e_shoff, header.data()); + LOAD_MEMBER(file_header_, e_flags, header.data()); + LOAD_MEMBER(file_header_, e_ehsize, header.data()); + LOAD_MEMBER(file_header_, e_phentsize, header.data()); + LOAD_MEMBER(file_header_, e_phnum, header.data()); + LOAD_MEMBER(file_header_, e_shentsize, header.data()); + LOAD_MEMBER(file_header_, e_shnum, header.data()); + LOAD_MEMBER(file_header_, e_shstrndx, header.data()); + return ::sapi::OkStatus(); +} + +::sapi::StatusOr ElfParser::ReadSectionHeader( + absl::string_view src) { + if (src.size() < sizeof(Elf64_Shdr)) { + return ::sapi::FailedPreconditionError( + absl::StrCat("invalid section header data: got ", src.size(), + " bytes, ", sizeof(Elf64_Shdr), " bytes expected.")); + } + Elf64_Shdr rv; + LOAD_MEMBER(rv, sh_name, src.data()); + LOAD_MEMBER(rv, sh_type, src.data()); + LOAD_MEMBER(rv, sh_flags, src.data()); + LOAD_MEMBER(rv, sh_addr, src.data()); + LOAD_MEMBER(rv, sh_offset, src.data()); + LOAD_MEMBER(rv, sh_size, src.data()); + LOAD_MEMBER(rv, sh_link, src.data()); + LOAD_MEMBER(rv, sh_info, src.data()); + LOAD_MEMBER(rv, sh_addralign, src.data()); + LOAD_MEMBER(rv, sh_entsize, src.data()); + return rv; +} + +::sapi::Status ElfParser::ReadSectionHeaders() { + if (file_header_.e_shoff > file_size_) { + return ::sapi::FailedPreconditionError( + absl::StrCat("invalid section header offset: ", file_header_.e_shoff)); + } + if (file_header_.e_shentsize != sizeof(Elf64_Shdr)) { + return ::sapi::FailedPreconditionError(absl::StrCat( + "section header entry size incorrect: ", file_header_.e_shentsize, + " bytes, ", sizeof(Elf64_Shdr), " expected.")); + } + if (file_header_.e_shnum > kMaxSectionHeaderEntries) { + return ::sapi::FailedPreconditionError( + absl::StrCat("too many section header entries: ", file_header_.e_shnum, + " limit: ", kMaxSectionHeaderEntries)); + } + std::string headers(file_header_.e_shentsize * file_header_.e_shnum, '\0'); + SAPI_RETURN_IF_ERROR(CheckedFSeek(elf_, file_header_.e_shoff, SEEK_SET)); + SAPI_RETURN_IF_ERROR(CheckedRead(&headers, elf_)); + section_headers_.resize(file_header_.e_shnum); + absl::string_view src = headers; + for (int i = 0; i < file_header_.e_shnum; ++i) { + SAPI_ASSIGN_OR_RETURN(section_headers_[i], ReadSectionHeader(src)); + src = src.substr(file_header_.e_shentsize); + } + return ::sapi::OkStatus(); +} + +::sapi::StatusOr ElfParser::ReadSectionContents(int idx) { + if (idx < 0 || idx >= section_headers_.size()) { + return ::sapi::FailedPreconditionError( + absl::StrCat("invalid section header index: ", idx)); + } + return ReadSectionContents(section_headers_.at(idx)); +} + +::sapi::StatusOr ElfParser::ReadSectionContents( + const Elf64_Shdr& section_header) { + auto offset = section_header.sh_offset; + if (offset > file_size_) { + return ::sapi::FailedPreconditionError( + absl::StrCat("invalid section offset: ", offset)); + } + auto size = section_header.sh_size; + if (size > kMaxSectionSize) { + return ::sapi::FailedPreconditionError( + absl::StrCat("section too big: ", size, " limit: ", kMaxSectionSize)); + } + std::string rv(size, '\0'); + SAPI_RETURN_IF_ERROR(CheckedFSeek(elf_, offset, SEEK_SET)); + SAPI_RETURN_IF_ERROR(CheckedRead(&rv, elf_)); + return rv; +} + +::sapi::StatusOr ElfParser::ReadProgramHeader( + absl::string_view src) { + if (src.size() < sizeof(Elf64_Phdr)) { + return ::sapi::FailedPreconditionError( + absl::StrCat("invalid program header data: got ", src.size(), + " bytes, ", sizeof(Elf64_Phdr), " bytes expected.")); + } + Elf64_Phdr rv; + LOAD_MEMBER(rv, p_type, src.data()); + LOAD_MEMBER(rv, p_flags, src.data()); + LOAD_MEMBER(rv, p_offset, src.data()); + LOAD_MEMBER(rv, p_vaddr, src.data()); + LOAD_MEMBER(rv, p_paddr, src.data()); + LOAD_MEMBER(rv, p_filesz, src.data()); + LOAD_MEMBER(rv, p_memsz, src.data()); + LOAD_MEMBER(rv, p_align, src.data()); + return rv; +} + +::sapi::Status ElfParser::ReadProgramHeaders() { + if (file_header_.e_phoff > file_size_) { + return ::sapi::FailedPreconditionError( + absl::StrCat("invalid program header offset: ", file_header_.e_phoff)); + } + if (file_header_.e_phentsize != sizeof(Elf64_Phdr)) { + return ::sapi::FailedPreconditionError(absl::StrCat( + "section header entry size incorrect: ", file_header_.e_phentsize, + " bytes, ", sizeof(Elf64_Phdr), " expected.")); + } + if (file_header_.e_phnum > kMaxProgramHeaderEntries) { + return ::sapi::FailedPreconditionError( + absl::StrCat("too many program header entries: ", file_header_.e_phnum, + " limit: ", kMaxProgramHeaderEntries)); + } + std::string headers(file_header_.e_phentsize * file_header_.e_phnum, '\0'); + SAPI_RETURN_IF_ERROR(CheckedFSeek(elf_, file_header_.e_phoff, SEEK_SET)); + SAPI_RETURN_IF_ERROR(CheckedRead(&headers, elf_)); + program_headers_.resize(file_header_.e_phnum); + absl::string_view src = headers; + for (int i = 0; i < file_header_.e_phnum; ++i) { + SAPI_ASSIGN_OR_RETURN(program_headers_[i], ReadProgramHeader(src)); + src = src.substr(file_header_.e_phentsize); + } + return ::sapi::OkStatus(); +} + +::sapi::Status ElfParser::ReadSymbolsFromSymtab(const Elf64_Shdr& symtab) { + if (symtab.sh_type != SHT_SYMTAB) { + return ::sapi::FailedPreconditionError("invalid symtab type"); + } + if (symtab.sh_entsize != sizeof(Elf64_Sym)) { + return ::sapi::InternalError( + absl::StrCat("invalid symbol entry size: ", symtab.sh_entsize)); + } + if ((symtab.sh_size % symtab.sh_entsize) != 0) { + return ::sapi::InternalError( + absl::StrCat("invalid symbol table size: ", symtab.sh_size)); + } + size_t symbol_entries = symtab.sh_size / symtab.sh_entsize; + if (symbol_entries > kMaxSymbolEntries - symbol_entries_read) { + return ::sapi::InternalError( + absl::StrCat("too many symbols: ", symbol_entries)); + } + symbol_entries_read += symbol_entries; + if (symtab.sh_link >= section_headers_.size()) { + return ::sapi::InternalError( + absl::StrCat("invalid symtab's strtab reference: ", symtab.sh_link)); + } + SAPI_RAW_VLOG(1, "Symbol table with %d entries found", symbol_entries); + SAPI_ASSIGN_OR_RETURN(std::string strtab, ReadSectionContents(symtab.sh_link)); + SAPI_ASSIGN_OR_RETURN(std::string symbols, ReadSectionContents(symtab)); + result_.symbols_.reserve(result_.symbols_.size() + symbol_entries); + for (absl::string_view src = symbols; !src.empty(); + src = src.substr(symtab.sh_entsize)) { + Elf64_Sym symbol; + LOAD_MEMBER(symbol, st_name, src.data()); + LOAD_MEMBER(symbol, st_info, src.data()); + LOAD_MEMBER(symbol, st_other, src.data()); + LOAD_MEMBER(symbol, st_shndx, src.data()); + LOAD_MEMBER(symbol, st_value, src.data()); + LOAD_MEMBER(symbol, st_size, src.data()); + if (symbol.st_shndx == SHN_UNDEF) { + // External symbol, not supported. + continue; + } + if (symbol.st_shndx == SHN_ABS) { + // Absolute value, not supported. + continue; + } + if (symbol.st_shndx >= section_headers_.size()) { + return ::sapi::FailedPreconditionError(absl::StrCat( + "invalid symbol data: section index: ", symbol.st_shndx)); + } + if (symbol.st_name >= strtab.size()) { + return ::sapi::FailedPreconditionError( + absl::StrCat("invalid name reference: REL", symbol.st_value)); + } + result_.symbols_.push_back( + {symbol.st_value, std::string(ReadName(symbol.st_name, strtab))}); + } + return ::sapi::OkStatus(); +} + +::sapi::Status ElfParser::ReadImportedLibrariesFromDynamic( + const Elf64_Shdr& dynamic) { + if (dynamic.sh_type != SHT_DYNAMIC) { + return ::sapi::FailedPreconditionError("invalid dynamic type"); + } + if (dynamic.sh_entsize != sizeof(Elf64_Dyn)) { + return ::sapi::InternalError( + absl::StrCat("invalid dynamic entry size: ", dynamic.sh_entsize)); + } + if ((dynamic.sh_size % dynamic.sh_entsize) != 0) { + return ::sapi::InternalError( + absl::StrCat("invalid dynamic table size: ", dynamic.sh_size)); + } + size_t entries = dynamic.sh_size / dynamic.sh_entsize; + if (entries > kMaxDynamicEntries - dynamic_entries_read) { + return ::sapi::InternalError( + absl::StrCat("too many dynamic entries: ", entries)); + } + dynamic_entries_read += entries; + if (dynamic.sh_link >= section_headers_.size()) { + return ::sapi::InternalError( + absl::StrCat("invalid dynamic's strtab reference: ", dynamic.sh_link)); + } + SAPI_RAW_VLOG(1, "Dynamic section with %d entries found", entries); + // strtab may be shared with symbols and therefore huge + const auto& strtab_section = section_headers_.at(dynamic.sh_link); + if (strtab_section.sh_offset > file_size_) { + return ::sapi::FailedPreconditionError(absl::StrCat( + "invalid symtab's strtab section offset: ", strtab_section.sh_offset)); + } + if (strtab_section.sh_size >= kMaxStrtabSize || + strtab_section.sh_size >= file_size_ || + strtab_section.sh_offset >= file_size_ - strtab_section.sh_size) { + return ::sapi::FailedPreconditionError( + absl::StrCat("symtab's strtab too big: ", strtab_section.sh_size)); + } + auto strtab_end = strtab_section.sh_offset + strtab_section.sh_size; + SAPI_ASSIGN_OR_RETURN(std::string dynamic_entries, ReadSectionContents(dynamic)); + for (absl::string_view src = dynamic_entries; !src.empty(); + src = src.substr(dynamic.sh_entsize)) { + Elf64_Dyn dyn; + LOAD_MEMBER(dyn, d_tag, src.data()); + LOAD_MEMBER(dyn, d_un.d_val, src.data()); + if (dyn.d_tag != DT_NEEDED) { + continue; + } + if (dyn.d_un.d_val >= strtab_section.sh_size) { + return ::sapi::FailedPreconditionError( + absl::StrCat("invalid name reference")); + } + auto offset = strtab_section.sh_offset + dyn.d_un.d_val; + SAPI_RETURN_IF_ERROR(CheckedFSeek(elf_, offset, SEEK_SET)); + std::string path(std::min(kMaxLibPathSize, strtab_end - offset), '\0'); + size_t size = fread(&path[0], 1, path.size(), elf_); + path.resize(size); + result_.imported_libraries_.push_back(path.substr(0, path.find('\0'))); + } + return ::sapi::OkStatus(); +} + +::sapi::StatusOr ElfParser::Parse(FILE* elf, uint32_t features) { + elf_ = elf; + // Basic sanity check. + if (features & ~(ElfFile::kAll)) { + return ::sapi::InvalidArgumentError("Unknown feature flags specified"); + } + SAPI_RETURN_IF_ERROR(ReadFileSize()); + SAPI_RETURN_IF_ERROR(ReadFileHeader()); + switch (file_header_.e_type) { + case ET_EXEC: + result_.position_independent_ = false; + break; + case ET_DYN: + result_.position_independent_ = true; + break; + default: + return ::sapi::FailedPreconditionError("not an executable: "); + } + if (features & ElfFile::kGetInterpreter) { + SAPI_RETURN_IF_ERROR(ReadProgramHeaders()); + std::string interpreter; + auto it = std::find_if( + program_headers_.begin(), program_headers_.end(), + [](const Elf64_Phdr& hdr) { return hdr.p_type == PT_INTERP; }); + // No interpreter usually means that the executable was statically linked. + if (it != program_headers_.end()) { + if (it->p_filesz > kMaxInterpreterSize) { + return ::sapi::FailedPreconditionError( + absl::StrCat("program interpeter path too long: ", it->p_filesz)); + } + SAPI_RETURN_IF_ERROR(CheckedFSeek(elf, it->p_offset, SEEK_SET)); + interpreter.resize(it->p_filesz, '\0'); + SAPI_RETURN_IF_ERROR(CheckedRead(&interpreter, elf)); + auto first_nul = interpreter.find_first_of('\0'); + if (first_nul != std::string::npos) { + interpreter.erase(first_nul); + } + } + result_.interpreter_ = std::move(interpreter); + } + + if (features & (ElfFile::kLoadSymbols | ElfFile::kLoadImportedLibraries)) { + SAPI_RETURN_IF_ERROR(ReadSectionHeaders()); + for (const auto& hdr : section_headers_) { + if (hdr.sh_type == SHT_SYMTAB && features & ElfFile::kLoadSymbols) { + SAPI_RETURN_IF_ERROR(ReadSymbolsFromSymtab(hdr)); + } + if (hdr.sh_type == SHT_DYNAMIC && + features & ElfFile::kLoadImportedLibraries) { + SAPI_RETURN_IF_ERROR(ReadImportedLibrariesFromDynamic(hdr)); + } + } + } + + return std::move(result_); +} + +::sapi::StatusOr ElfFile::ParseFromFile(const std::string& filename, + uint32_t features) { + std::unique_ptr elf{fopen(filename.c_str(), "r"), + [](FILE* f) { fclose(f); }}; + if (!elf) { + return ::sapi::UnknownError( + absl::StrCat("cannot open file: ", filename, ": ", StrError(errno))); + } + + return ElfParser().Parse(elf.get(), features); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/minielf.h b/sandboxed_api/sandbox2/util/minielf.h new file mode 100644 index 0000000..b42b7d7 --- /dev/null +++ b/sandboxed_api/sandbox2/util/minielf.h @@ -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. + +#ifndef SANDBOXED_API_SANDBOX2_UTIL_MINIELF_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_MINIELF_H_ + +#include +#include +#include +#include + +#include "sandboxed_api/util/statusor.h" + +namespace sandbox2 { + +// Minimal implementation of an ELF file parser to read the program interpreter. +// Only understands 64-bit ELFs. +class ElfFile { + public: + struct Symbol { + uint64_t address; + std::string name; + }; + + static ::sapi::StatusOr ParseFromFile(const std::string& filename, + uint32_t features); + + int64_t file_size() const { return file_size_; } + const std::string& interpreter() const { return interpreter_; } + const std::vector symbols() const { return symbols_; } + const std::vector imported_libraries() const { + return imported_libraries_; + } + bool position_independent() const { return position_independent_; } + + static constexpr uint32_t kGetInterpreter = 1 << 0; + static constexpr uint32_t kLoadSymbols = 1 << 1; + static constexpr uint32_t kLoadImportedLibraries = 1 << 2; + static constexpr uint32_t kAll = + kGetInterpreter | kLoadSymbols | kLoadImportedLibraries; + + private: + friend class ElfParser; + + bool position_independent_; + int64_t file_size_ = 0; + std::string interpreter_; + std::vector symbols_; + std::vector imported_libraries_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UTIL_MINIELF_H_ diff --git a/sandboxed_api/sandbox2/util/minielf_test.cc b/sandboxed_api/sandbox2/util/minielf_test.cc new file mode 100644 index 0000000..5d6af60 --- /dev/null +++ b/sandboxed_api/sandbox2/util/minielf_test.cc @@ -0,0 +1,99 @@ +// 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/util/minielf.h" + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/maps_parser.h" +#include "sandboxed_api/util/status_matchers.h" + +using sapi::IsOk; +using ::testing::Eq; +using ::testing::IsTrue; +using ::testing::Not; +using ::testing::StrEq; + +extern "C" void ExportedFunctionName() { + // Don't do anything - used to generate a symbol. +} + +namespace sandbox2 { +namespace { + +TEST(MinielfTest, Chrome70) { + SAPI_ASSERT_OK_AND_ASSIGN( + ElfFile elf, + ElfFile::ParseFromFile( + GetTestSourcePath("sandbox2/util/testdata/chrome_grte_header"), + ElfFile::kGetInterpreter)); + EXPECT_THAT(elf.interpreter(), StrEq("/usr/grte/v4/ld64")); +} + +TEST(MinielfTest, SymbolResolutionWorks) { + SAPI_ASSERT_OK_AND_ASSIGN( + ElfFile elf, + ElfFile::ParseFromFile("/proc/self/exe", ElfFile::kLoadSymbols)); + ASSERT_THAT(elf.position_independent(), IsTrue()); + + // Load /proc/self/maps to take ASLR into account. + char maps_buffer[1024 * 1024]{}; + FILE *f = fopen("/proc/self/maps", "r"); + ASSERT_THAT(f, Not(Eq(nullptr))); + fread(maps_buffer, 1, sizeof(maps_buffer), f); + fclose(f); + + auto maps_or = ParseProcMaps(maps_buffer); + ASSERT_THAT(maps_or.status(), IsOk()); + std::vector maps = maps_or.ValueOrDie(); + + // Find maps entry that covers this entry. + uint64_t function_address = reinterpret_cast(ExportedFunctionName); + bool entry_found = false; + for (const auto &entry : maps) { + if (entry.start <= function_address && entry.end > function_address) { + entry_found = true; + function_address -= entry.start; + break; + } + } + ASSERT_THAT(entry_found, IsTrue()); + + uint64_t exported_function_name__symbol_value = 0; + + for (const auto &s : elf.symbols()) { + if (s.name == "ExportedFunctionName") { + exported_function_name__symbol_value = s.address; + break; + } + } + + EXPECT_THAT(exported_function_name__symbol_value, function_address); +} + +TEST(MinielfTest, ImportedLibraries) { + SAPI_ASSERT_OK_AND_ASSIGN( + ElfFile elf, ElfFile::ParseFromFile( + GetTestSourcePath("sandbox2/util/testdata/hello_world"), + ElfFile::kLoadImportedLibraries)); + std::vector imported_libraries = {"libc.so.6"}; + EXPECT_THAT(elf.imported_libraries(), Eq(imported_libraries)); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/path.cc b/sandboxed_api/sandbox2/util/path.cc new file mode 100644 index 0000000..223ae8c --- /dev/null +++ b/sandboxed_api/sandbox2/util/path.cc @@ -0,0 +1,153 @@ +// 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/util/path.h" + +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/strip.h" + +namespace sandbox2 { +namespace file { +namespace internal { + +constexpr char kPathSeparator[] = "/"; + +std::string JoinPathImpl(std::initializer_list paths) { + std::string result; + for (const auto& path : paths) { + if (path.empty()) { + continue; + } + if (result.empty()) { + absl::StrAppend(&result, path); + continue; + } + const auto comp = absl::StripPrefix(path, kPathSeparator); + if (absl::EndsWith(result, kPathSeparator)) { + absl::StrAppend(&result, comp); + } else { + absl::StrAppend(&result, kPathSeparator, comp); + } + } + return result; +} + +} // namespace internal + +bool IsAbsolutePath(absl::string_view path) { + return !path.empty() && path[0] == '/'; +} + +std::pair SplitPath( + absl::string_view path) { + const auto pos = path.find_last_of('/'); + + // Handle the case with no '/' in 'path'. + if (pos == absl::string_view::npos) { + return {path.substr(0, 0), path}; + } + + // Handle the case with a single leading '/' in 'path'. + if (pos == 0) { + return {path.substr(0, 1), absl::ClippedSubstr(path, 1)}; + } + return {path.substr(0, pos), absl::ClippedSubstr(path, pos + 1)}; +} + +std::string CleanPath(const absl::string_view unclean_path) { + std::string path = std::string(unclean_path); + const char* src = path.c_str(); + std::string::iterator dst = path.begin(); + + // Check for absolute path and determine initial backtrack limit. + const bool is_absolute_path = *src == '/'; + if (is_absolute_path) { + *dst++ = *src++; + while (*src == '/') { + ++src; + } + } + std::string::const_iterator backtrack_limit = dst; + + // Process all parts + while (*src) { + bool parsed = false; + + if (src[0] == '.') { + // 1dot ".", check for END or SEP. + if (src[1] == '/' || !src[1]) { + if (*++src) { + ++src; + } + parsed = true; + } else if (src[1] == '.' && (src[2] == '/' || !src[2])) { + // 2dot END or SEP (".." | "../"). + src += 2; + if (dst != backtrack_limit) { + // We can backtrack the previous part + for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) { + // Empty. + } + } else if (!is_absolute_path) { + // Failed to backtrack and we can't skip it either. Rewind and copy. + src -= 2; + *dst++ = *src++; + *dst++ = *src++; + if (*src) { + *dst++ = *src; + } + // We can never backtrack over a copied "../" part so set new limit. + backtrack_limit = dst; + } + if (*src) { + ++src; + } + parsed = true; + } + } + + // If not parsed, copy entire part until the next SEP or EOS. + if (!parsed) { + while (*src && *src != '/') { + *dst++ = *src++; + } + if (*src) { + *dst++ = *src++; + } + } + + // Skip consecutive SEP occurrences. + while (*src == '/') { + ++src; + } + } + + // Calculate and check the length of the cleaned path. + int path_length = dst - path.begin(); + if (path_length != 0) { + // Remove trailing '/' except if it is root path ("/" ==> path_length := 1). + if (path_length > 1 && path[path_length - 1] == '/') { + --path_length; + } + path.resize(path_length); + } else { + // The cleaned path is empty; assign "." as per the spec. + path.assign(1, '.'); + } + return path; +} + +} // namespace file +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/path.h b/sandboxed_api/sandbox2/util/path.h new file mode 100644 index 0000000..7d72415 --- /dev/null +++ b/sandboxed_api/sandbox2/util/path.h @@ -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. + +#ifndef SANDBOXED_API_SANDBOX2_UTIL_PATH_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_PATH_H_ + +#include +#include +#include + +#include "absl/strings/string_view.h" +namespace sandbox2 { +namespace file { + +namespace internal { +// Not part of the public API. +std::string JoinPathImpl(std::initializer_list paths); +} // namespace internal + +// Joins multiple paths together using the platform-specific path separator. +// Arguments must be convertible to absl::string_view. +template +inline std::string JoinPath(const T&... args) { + return internal::JoinPathImpl({args...}); +} + +// Return true if path is absolute. +bool IsAbsolutePath(absl::string_view path); + +// Returns the parts of the path, split on the final "/". If there is no +// "/" in the path, the first part of the output is empty and the second +// is the input. If the only "/" in the path is the first character, it is +// the first part of the output. +std::pair SplitPath( + absl::string_view path); + +// Collapses duplicate "/"s, resolve ".." and "." path elements, removes +// trailing "/". +// +// NOTE: This respects relative vs. absolute paths, but does not +// invoke any system calls in order to resolve relative paths to the actual +// working directory. That is, this is purely a string manipulation, completely +// independent of process state. +std::string CleanPath(absl::string_view path); + +} // namespace file +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UTIL_PATH_H_ diff --git a/sandboxed_api/sandbox2/util/path_test.cc b/sandboxed_api/sandbox2/util/path_test.cc new file mode 100644 index 0000000..1993480 --- /dev/null +++ b/sandboxed_api/sandbox2/util/path_test.cc @@ -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 "sandboxed_api/sandbox2/util/path.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Pair; +using testing::StrEq; + +namespace sandbox2 { +namespace { + +TEST(PathTest, ArgumentTypes) { + // JoinPath must be able to accept arguments that are compatible with + // absl::string_view. So test a few of them here. + const char char_array[] = "a"; + const char* char_ptr = "b"; + std::string string_type = "c"; + absl::string_view sp_type = "d"; + + EXPECT_THAT(file::JoinPath(char_array, char_ptr, string_type, sp_type), + StrEq("a/b/c/d")); +} + +TEST(PathTest, JoinPath) { + EXPECT_THAT(file::JoinPath("/foo", "bar"), StrEq("/foo/bar")); + EXPECT_THAT(file::JoinPath("foo", "bar"), StrEq("foo/bar")); + EXPECT_THAT(file::JoinPath("foo", "/bar"), StrEq("foo/bar")); + EXPECT_THAT(file::JoinPath("/foo", "/bar"), StrEq("/foo/bar")); + + EXPECT_THAT(file::JoinPath("", "/bar"), StrEq("/bar")); + EXPECT_THAT(file::JoinPath("", "bar"), StrEq("bar")); + EXPECT_THAT(file::JoinPath("/foo", ""), StrEq("/foo")); + + EXPECT_THAT(file::JoinPath("/foo/bar/baz/", "/blah/blink/biz"), + StrEq("/foo/bar/baz/blah/blink/biz")); + + EXPECT_THAT(file::JoinPath("/foo", "bar", "baz"), StrEq("/foo/bar/baz")); + EXPECT_THAT(file::JoinPath("foo", "bar", "baz"), StrEq("foo/bar/baz")); + EXPECT_THAT(file::JoinPath("/foo", "bar", "baz", "blah"), + StrEq("/foo/bar/baz/blah")); + EXPECT_THAT(file::JoinPath("/foo", "bar", "/baz", "blah"), + StrEq("/foo/bar/baz/blah")); + EXPECT_THAT(file::JoinPath("/foo", "/bar/", "/baz", "blah"), + StrEq("/foo/bar/baz/blah")); + EXPECT_THAT(file::JoinPath("/foo", "/bar/", "baz", "blah"), + StrEq("/foo/bar/baz/blah")); + + EXPECT_THAT(file::JoinPath("/", "a"), StrEq("/a")); + EXPECT_THAT(file::JoinPath(), StrEq("")); +} + +TEST(PathTest, SplitPath) { + // We cannot write the type directly within the EXPECT, because the ',' breaks + // the macro. + EXPECT_THAT(file::SplitPath("/hello/"), Pair("/hello", "")); + EXPECT_THAT(file::SplitPath("/hello"), Pair("/", "hello")); + EXPECT_THAT(file::SplitPath("hello/world"), Pair("hello", "world")); + EXPECT_THAT(file::SplitPath("hello/"), Pair("hello", "")); + EXPECT_THAT(file::SplitPath("world"), Pair("", "world")); + EXPECT_THAT(file::SplitPath("/"), Pair("/", "")); + EXPECT_THAT(file::SplitPath(""), Pair("", "")); +} + +TEST(PathTest, CleanPath) { + EXPECT_THAT(file::CleanPath(""), StrEq(".")); + EXPECT_THAT(file::CleanPath("x"), StrEq("x")); + EXPECT_THAT(file::CleanPath("/a/b/c/d"), StrEq("/a/b/c/d")); + EXPECT_THAT(file::CleanPath("/a/b/c/d/"), StrEq("/a/b/c/d")); + EXPECT_THAT(file::CleanPath("/a//b"), StrEq("/a/b")); + EXPECT_THAT(file::CleanPath("//a//b/"), StrEq("/a/b")); + EXPECT_THAT(file::CleanPath("/.."), StrEq("/")); + EXPECT_THAT(file::CleanPath("/././././"), StrEq("/")); + EXPECT_THAT(file::CleanPath("/a/b/.."), StrEq("/a")); + EXPECT_THAT(file::CleanPath("/a/b/../../.."), StrEq("/")); + EXPECT_THAT(file::CleanPath("//a//b/..////../..//"), StrEq("/")); + EXPECT_THAT(file::CleanPath("//a//../x//"), StrEq("/x")); + EXPECT_THAT(file::CleanPath("../../a/b/../c"), StrEq("../../a/c")); + EXPECT_THAT(file::CleanPath("../../a/b/../c/../.."), StrEq("../..")); + EXPECT_THAT(file::CleanPath("foo/../../../bar"), StrEq("../../bar")); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/runfiles.cc b/sandboxed_api/sandbox2/util/runfiles.cc new file mode 100644 index 0000000..3b88db7 --- /dev/null +++ b/sandboxed_api/sandbox2/util/runfiles.cc @@ -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. + +#include + +#include "absl/strings/str_format.h" +#include "sandboxed_api/sandbox2/util/path.h" +#include "sandboxed_api/sandbox2/util/runfiles.h" +#include "sandboxed_api/util/flag.h" +#include "sandboxed_api/util/raw_logging.h" +#include "tools/cpp/runfiles/runfiles.h" + +using bazel::tools::cpp::runfiles::Runfiles; + +namespace sandbox2 { + +std::string GetDataDependencyFilePath(absl::string_view relative_path) { + static Runfiles* runfiles = []() { + std::string error; + auto* runfiles = Runfiles::Create(gflags::GetArgv0(), &error); + SAPI_RAW_CHECK(runfiles != nullptr, "%s", error); + + // Setup environment for child processes. + for (const auto& entry : runfiles->EnvVars()) { + setenv(entry.first.c_str(), entry.second.c_str(), 1 /* overwrite */); + } + return runfiles; + }(); + return runfiles->Rlocation(std::string(relative_path)); +} + +std::string GetInternalDataDependencyFilePath(absl::string_view relative_path) { + return GetDataDependencyFilePath( + file::JoinPath("com_google_sandboxed_api/sandboxed_api", relative_path)); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/runfiles.h b/sandboxed_api/sandbox2/util/runfiles.h new file mode 100644 index 0000000..be1e488 --- /dev/null +++ b/sandboxed_api/sandbox2/util/runfiles.h @@ -0,0 +1,34 @@ +// 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_UTIL_RUNFILES_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_RUNFILES_H_ + +#include + +#include "absl/strings/string_view.h" + +namespace sandbox2 { + +// Returns the file path pointing to a resource file. The relative_path argument +// should be relative to the runfiles directory. +std::string GetDataDependencyFilePath(absl::string_view relative_path); + +// Like GetDataDependencyFilePath(), but prepends the location of the Sandbox2 +// root runfiles path. +std::string GetInternalDataDependencyFilePath(absl::string_view relative_path); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UTIL_RUNFILES_H_ diff --git a/sandboxed_api/sandbox2/util/strerror.cc b/sandboxed_api/sandbox2/util/strerror.cc new file mode 100644 index 0000000..754e5eb --- /dev/null +++ b/sandboxed_api/sandbox2/util/strerror.cc @@ -0,0 +1,62 @@ +// 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/util/strerror.h" + +#include // For strerror_r + +#include +#include + +#include "absl/base/attributes.h" +#include "absl/strings/str_cat.h" + +namespace sandbox2 { +namespace { + +// Only one of these overloads will be used in any given build, as determined by +// the return type of strerror_r(): char* (for GNU), or int (for XSI). See 'man +// strerror_r' for more details. +ABSL_ATTRIBUTE_UNUSED const char* StrErrorR(char* (*strerror_r)(int, char*, + size_t), + int errnum, char* buf, + size_t buflen) { + return strerror_r(errnum, buf, buflen); +} + +// The XSI version (most portable). +ABSL_ATTRIBUTE_UNUSED const char* StrErrorR(int (*strerror_r)(int, char*, + size_t), + int errnum, char* buf, + size_t buflen) { + if (strerror_r(errnum, buf, buflen)) { + *buf = '\0'; + } + return buf; +} + +} // namespace + +std::string StrError(int errnum) { + const int saved_errno = errno; + char buf[100]; + const char* str = StrErrorR(strerror_r, errnum, buf, sizeof(buf)); + if (*str == '\0') { + return absl::StrCat("Unknown error ", errnum); + } + errno = saved_errno; + return str; +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/strerror.h b/sandboxed_api/sandbox2/util/strerror.h new file mode 100644 index 0000000..20e0ee8 --- /dev/null +++ b/sandboxed_api/sandbox2/util/strerror.h @@ -0,0 +1,30 @@ +// 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_UTIL_STRERROR_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_STRERROR_H_ + +#include + +namespace sandbox2 { + +// Returns a human-readable string describing the given POSIX error code. This +// is a portable and thread-safe alternative to strerror(). If the error code is +// not translatable, the string will be "Unknown error nnn". errno will not be +// modified by this call. This function is thread-safe. +std::string StrError(int errnum); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_STRERROR_H_ diff --git a/sandboxed_api/sandbox2/util/strerror_test.cc b/sandboxed_api/sandbox2/util/strerror_test.cc new file mode 100644 index 0000000..d1e1bf8 --- /dev/null +++ b/sandboxed_api/sandbox2/util/strerror_test.cc @@ -0,0 +1,81 @@ +// 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/util/strerror.h" + +#include +#include +#include +#include // NOLINT(build/c++11) +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/match.h" + +using ::testing::Eq; +using ::testing::StrEq; + +namespace sandbox2 { +namespace { + +TEST(StrErrorTest, ValidErrorCode) { + errno = EAGAIN; + EXPECT_THAT(StrError(EINTR), StrEq(strerror(EINTR))); + EXPECT_THAT(errno, Eq(EAGAIN)); +} + +TEST(StrErrorTest, InvalidErrorCode) { + errno = EBUSY; + EXPECT_THAT(StrError(-1), StrEq("Unknown error -1")); + EXPECT_THAT(errno, Eq(EBUSY)); +} + +TEST(StrErrorTest, MultipleThreads) { + // In this test, we will start up 2 threads and have each one call StrError + // 1000 times, each time with a different errnum. We expect that + // StrError(errnum) will return a std::string equal to the one returned by + // strerror(errnum), if the code is known. Since strerror is known to be + // thread-hostile, collect all the expected strings up front. + constexpr int kNumCodes = 1000; + std::vector expected_strings(kNumCodes); + for (int i = 0; i < kNumCodes; ++i) { + expected_strings[i] = strerror(i); + } + + std::atomic counter{0}; + auto thread_fun = [&counter, &expected_strings]() { + for (int i = 0; i < kNumCodes; ++i) { + ++counter; + std::string value = StrError(i); + if (!absl::StartsWith(value, "Unknown error ")) { + EXPECT_THAT(StrError(i), StrEq(expected_strings[i])); + } + } + }; + + constexpr int kNumThreads = 100; + std::vector threads; + for (int i = 0; i < kNumThreads; ++i) { + threads.push_back(std::thread(thread_fun)); + } + for (auto& thread : threads) { + thread.join(); + } + + EXPECT_THAT(counter, Eq(kNumThreads * kNumCodes)); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/temp_file.cc b/sandboxed_api/sandbox2/util/temp_file.cc new file mode 100644 index 0000000..9f3edcc --- /dev/null +++ b/sandboxed_api/sandbox2/util/temp_file.cc @@ -0,0 +1,66 @@ +// 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/util/temp_file.h" + +#include +#include +#include +#include + +#include +#include + +#include "absl/strings/str_cat.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/canonical_errors.h" + +namespace sandbox2 { + +namespace { +constexpr absl::string_view kMktempSuffix = "XXXXXX"; +} // namespace + +sapi::StatusOr> CreateNamedTempFile( + absl::string_view prefix) { + std::string name_template = absl::StrCat(prefix, kMktempSuffix); + int fd = mkstemp(&name_template[0]); + if (fd < 0) { + return sapi::UnknownError(absl::StrCat("mkstemp():", StrError(errno))); + } + return std::pair{std::move(name_template), fd}; +} + +sapi::StatusOr CreateNamedTempFileAndClose(absl::string_view prefix) { + auto result_or = CreateNamedTempFile(prefix); + if (result_or.ok()) { + std::string path; + int fd; + std::tie(path, fd) = result_or.ValueOrDie(); + close(fd); + return path; + } + return result_or.status(); +} + +sapi::StatusOr CreateTempDir(absl::string_view prefix) { + std::string name_template = absl::StrCat(prefix, kMktempSuffix); + if (mkdtemp(&name_template[0]) == nullptr) { + return sapi::UnknownError(absl::StrCat("mkdtemp():", StrError(errno))); + } + return name_template; +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/temp_file.h b/sandboxed_api/sandbox2/util/temp_file.h new file mode 100644 index 0000000..a0e6c7d --- /dev/null +++ b/sandboxed_api/sandbox2/util/temp_file.h @@ -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. + +#ifndef SANDBOXED_API_SANDBOX2_UTIL_TEMP_FILE_H_ +#define SANDBOXED_API_SANDBOX2_UTIL_TEMP_FILE_H_ + +#include "sandboxed_api/util/statusor.h" + +namespace sandbox2 { + +// Creates a temporary file under a path starting with prefix. File is not +// unlinked and its path is returned together with an open fd. +sapi::StatusOr> CreateNamedTempFile( + absl::string_view prefix); + +// Creates a temporary file under a path starting with prefix. File is not +// unlinked and its path is returned. FD of the created file is closed just +// after creation. +sapi::StatusOr CreateNamedTempFileAndClose(absl::string_view prefix); + +// Creates a temporary directory under a path starting with prefix. +// Returns the path of the created directory. +sapi::StatusOr CreateTempDir(absl::string_view prefix); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_UTIL_TEMP_FILE_H_ diff --git a/sandboxed_api/sandbox2/util/temp_file_test.cc b/sandboxed_api/sandbox2/util/temp_file_test.cc new file mode 100644 index 0000000..0e38d06 --- /dev/null +++ b/sandboxed_api/sandbox2/util/temp_file_test.cc @@ -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. + +#include "sandboxed_api/sandbox2/util/temp_file.h" + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/path.h" +#include "sandboxed_api/util/status_matchers.h" + +using sapi::IsOk; +using testing::Eq; +using testing::IsTrue; +using testing::Ne; +using testing::StartsWith; + +namespace sandbox2 { +namespace { + +TEST(TempFileTest, CreateTempDirTest) { + const std::string prefix = GetTestTempPath("MakeTempDirTest_"); + auto result_or = CreateTempDir(prefix); + ASSERT_THAT(result_or.status(), IsOk()); + std::string path = result_or.ValueOrDie(); + EXPECT_THAT(path, StartsWith(prefix)); + EXPECT_THAT(file_util::fileops::Exists(path, false), IsTrue()); + result_or = CreateTempDir("non_existing_dir/prefix"); + EXPECT_THAT(result_or, StatusIs(sapi::StatusCode::kUnknown)); +} + +TEST(TempFileTest, MakeTempFileTest) { + const std::string prefix = GetTestTempPath("MakeTempDirTest_"); + auto result_or = CreateNamedTempFile(prefix); + ASSERT_THAT(result_or.status(), IsOk()); + std::string path; + int fd; + std::tie(path, fd) = result_or.ValueOrDie(); + EXPECT_THAT(path, StartsWith(prefix)); + EXPECT_THAT(file_util::fileops::Exists(path, false), IsTrue()); + EXPECT_THAT(fcntl(fd, F_GETFD), Ne(-1)); + EXPECT_THAT(close(fd), Eq(0)); + result_or = CreateNamedTempFile("non_existing_dir/prefix"); + EXPECT_THAT(result_or, StatusIs(sapi::StatusCode::kUnknown)); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/util/testdata/chrome_grte_header b/sandboxed_api/sandbox2/util/testdata/chrome_grte_header new file mode 100644 index 0000000..61b4ce6 Binary files /dev/null and b/sandboxed_api/sandbox2/util/testdata/chrome_grte_header differ diff --git a/sandboxed_api/sandbox2/util/testdata/hello_world b/sandboxed_api/sandbox2/util/testdata/hello_world new file mode 100755 index 0000000..2d470b9 Binary files /dev/null and b/sandboxed_api/sandbox2/util/testdata/hello_world differ diff --git a/sandboxed_api/sandbox2/util_test.cc b/sandboxed_api/sandbox2/util_test.cc new file mode 100644 index 0000000..fa04599 --- /dev/null +++ b/sandboxed_api/sandbox2/util_test.cc @@ -0,0 +1,52 @@ +// 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/util.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/sandbox2/util/path.h" + +using testing::Gt; +using testing::IsTrue; + +namespace sandbox2 { +namespace util { +namespace { + +constexpr char kTestDir[] = "a/b/c"; + +TEST(UtilTest, TestCreateDirSuccess) { + EXPECT_THAT(CreateDirRecursive(GetTestTempPath(kTestDir), 0700), IsTrue()); +} + +TEST(UtilTest, TestCreateDirExistSuccess) { + const std::string test_dir = GetTestTempPath(kTestDir); + EXPECT_THAT(CreateDirRecursive(test_dir, 0700), IsTrue()); + EXPECT_THAT(CreateDirRecursive(test_dir, 0700), IsTrue()); +} + +TEST(UtilTest, TestCreateMemFd) { + int fd = 0; + ASSERT_THAT(CreateMemFd(&fd), IsTrue()); + EXPECT_THAT(fd, Gt(1)); + close(fd); +} + +} // namespace +} // namespace util +} // namespace sandbox2 diff --git a/sandboxed_api/sapi_test.cc b/sandboxed_api/sapi_test.cc new file mode 100644 index 0000000..43b625a --- /dev/null +++ b/sandboxed_api/sapi_test.cc @@ -0,0 +1,261 @@ +// 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 + +#include "benchmark/benchmark.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.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/examples/sum/lib/sandbox.h" +#include "sandboxed_api/examples/sum/lib/sum-sapi.sapi.h" +#include "sandboxed_api/examples/sum/lib/sum-sapi_embed.h" +#include "sandboxed_api/transaction.h" +#include "sandboxed_api/util/status_matchers.h" +#include "sandboxed_api/util/status.h" + +using ::sapi::IsOk; +using ::sapi::StatusIs; +using ::testing::Eq; +using ::testing::HasSubstr; + +namespace sapi { +namespace { + +// Functions that will be used during the benchmarks: + +// Function causing no load in the sandboxee. +sapi::Status InvokeNop(Sandbox* sandbox) { + StringopApi api(sandbox); + return api.nop(); +} + +// Function that makes use of our special protobuf (de)-serialization code +// inside SAPI (including the back-synchronization of the structure). +sapi::Status InvokeStringReversal(Sandbox* sandbox) { + StringopApi api(sandbox); + stringop::StringReverse proto; + proto.set_input("Hello"); + v::Proto pp(proto); + SAPI_ASSIGN_OR_RETURN(int return_code, api.pb_reverse_string(pp.PtrBoth())); + TRANSACTION_FAIL_IF_NOT(return_code != 0, "pb_reverse_string failed"); + std::unique_ptr pb_result = pp.GetProtoCopy(); + TRANSACTION_FAIL_IF_NOT(pb_result, "Could not deserialize pb result"); + TRANSACTION_FAIL_IF_NOT(pb_result->output() == "olleH", "Incorrect output"); + return sapi::OkStatus(); +} + +// Benchmark functions: + +// Restart SAPI sandbox by letting the sandbox object go out of scope. +// Minimal case for measuring the minimum overhead of restarting the sandbox. +void BenchmarkSandboxRestartOverhead(benchmark::State& state) { + for (auto _ : state) { + BasicTransaction st(absl::make_unique()); + // Invoke nop() to make sure that our sandbox is running. + EXPECT_THAT(st.Run(InvokeNop), IsOk()); + } +} +BENCHMARK(BenchmarkSandboxRestartOverhead); + +void BenchmarkSandboxRestartForkserverOverhead(benchmark::State& state) { + sapi::BasicTransaction st{absl::make_unique()}; + for (auto _ : state) { + EXPECT_THAT(st.Run(InvokeNop), IsOk()); + EXPECT_THAT(st.GetSandbox()->Restart(true), IsOk()); + } +} +BENCHMARK(BenchmarkSandboxRestartForkserverOverhead); + +void BenchmarkSandboxRestartForkserverOverheadForced(benchmark::State& state) { + sapi::BasicTransaction st{absl::make_unique()}; + for (auto _ : state) { + EXPECT_THAT(st.Run(InvokeNop), IsOk()); + EXPECT_THAT(st.GetSandbox()->Restart(false), IsOk()); + } +} +BENCHMARK(BenchmarkSandboxRestartForkserverOverheadForced); + +// Reuse the sandbox. Used to measure the overhead of the call invocation. +void BenchmarkCallOverhead(benchmark::State& state) { + BasicTransaction st(absl::make_unique()); + for (auto _ : state) { + EXPECT_THAT(st.Run(InvokeNop), IsOk()); + } +} +BENCHMARK(BenchmarkCallOverhead); + +// Make use of protobufs. +void BenchmarkProtobufHandling(benchmark::State& state) { + BasicTransaction st(absl::make_unique()); + for (auto _ : state) { + EXPECT_THAT(st.Run(InvokeStringReversal), IsOk()); + } +} +BENCHMARK(BenchmarkProtobufHandling); + +// Measure overhead of synchronizing data. +void BenchmarkIntDataSynchronization(benchmark::State& state) { + auto sandbox = absl::make_unique(); + ASSERT_THAT(sandbox->Init(), IsOk()); + + long current_val = 0; // NOLINT + v::Long long_var; + // Allocate remote memory. + ASSERT_THAT(sandbox->Allocate(&long_var, false), IsOk()); + + for (auto _ : state) { + // Write current_val to the process. + long_var.SetValue(current_val); + EXPECT_THAT(sandbox->TransferToSandboxee(&long_var), IsOk()); + // Invalidate value to make sure that the next call + // is not simply a noop. + long_var.SetValue(-1); + // Read value back. + EXPECT_THAT(sandbox->TransferFromSandboxee(&long_var), IsOk()); + EXPECT_THAT(long_var.GetValue(), Eq(current_val)); + + current_val++; + } +} +BENCHMARK(BenchmarkIntDataSynchronization); + +// Test whether stack trace generation works. +TEST(SAPITest, HasStackTraces) { + auto sandbox = absl::make_unique(); + ASSERT_THAT(sandbox->Init(), IsOk()); + StringopApi api(sandbox.get()); + EXPECT_THAT(api.violate(), StatusIs(sapi::StatusCode::kUnavailable)); + const auto& result = sandbox->AwaitResult(); + EXPECT_THAT(result.GetStackTrace(), HasSubstr("violate")); + EXPECT_THAT(result.final_status(), Eq(sandbox2::Result::VIOLATION)); +} + +// Various tests: + +// Leaks a file descriptor inside the sandboxee. +int leak_file_descriptor(sapi::Sandbox* sandbox, const char* path) { + int raw_fd = open(path, O_RDONLY); + sapi::v::Fd fd(raw_fd); // Takes ownership of the raw fd. + EXPECT_THAT(sandbox->TransferToSandboxee(&fd), IsOk()); + // We want to leak the remote FD. The local FD will still be closed. + fd.OwnRemoteFd(false); + return fd.GetRemoteFd(); +} + +// Make sure that restarting the sandboxee works (= fresh set of FDs). +TEST(SandboxTest, RestartSandboxFD) { + sapi::BasicTransaction st{absl::make_unique()}; + + auto test_body = [](sapi::Sandbox* sandbox) -> sapi::Status { + // Open some FDs and check their value. + EXPECT_THAT(leak_file_descriptor(sandbox, "/proc/self/exe"), Eq(3)); + EXPECT_THAT(leak_file_descriptor(sandbox, "/proc/self/exe"), Eq(4)); + SAPI_RETURN_IF_ERROR(sandbox->Restart(false)); + // We should have a fresh sandbox now = FDs open previously should be + // closed now. + EXPECT_THAT(leak_file_descriptor(sandbox, "/proc/self/exe"), Eq(3)); + return sapi::OkStatus(); + }; + + EXPECT_THAT(st.Run(test_body), IsOk()); +} + +TEST(SandboxTest, RestartTransactionSandboxFD) { + sapi::BasicTransaction st{absl::make_unique()}; + + EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status { + EXPECT_THAT(leak_file_descriptor(sandbox, "/proc/self/exe"), Eq(3)); + return sapi::OkStatus(); + }), + IsOk()); + + EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status { + EXPECT_THAT(leak_file_descriptor(sandbox, "/proc/self/exe"), Eq(4)); + return sapi::OkStatus(); + }), + IsOk()); + + EXPECT_THAT(st.Restart(), IsOk()); + + EXPECT_THAT(st.Run([](sapi::Sandbox* sandbox) -> sapi::Status { + EXPECT_THAT(leak_file_descriptor(sandbox, "/proc/self/exe"), Eq(3)); + return sapi::OkStatus(); + }), + IsOk()); +} + +// Make sure we can recover from a dying sandbox. +TEST(SandboxTest, RestartSandboxAfterCrash) { + sapi::BasicTransaction st{absl::make_unique()}; + + auto test_body = [](sapi::Sandbox* sandbox) -> sapi::Status { + SumApi sumapi(sandbox); + // Crash the sandbox. + EXPECT_THAT(sumapi.crash(), StatusIs(sapi::StatusCode::kUnavailable)); + EXPECT_THAT(sumapi.sum(1, 2).status(), + StatusIs(sapi::StatusCode::kUnavailable)); + EXPECT_THAT(sandbox->AwaitResult().final_status(), + Eq(sandbox2::Result::SIGNALED)); + + // Restart the sandbox. + EXPECT_THAT(sandbox->Restart(false), IsOk()); + // The sandbox should now be responsive again. + auto result_or = sumapi.sum(1, 2); + EXPECT_THAT(result_or.ValueOrDie(), Eq(3)); + return sapi::OkStatus(); + }; + + EXPECT_THAT(st.Run(test_body), IsOk()); +} + +TEST(SandboxTest, RestartSandboxAfterViolation) { + sapi::BasicTransaction st{absl::make_unique()}; + + auto test_body = [](sapi::Sandbox* sandbox) -> sapi::Status { + SumApi sumapi(sandbox); + // Violate the sandbox policy. + EXPECT_THAT(sumapi.violate(), StatusIs(sapi::StatusCode::kUnavailable)); + EXPECT_THAT(sumapi.sum(1, 2).status(), + StatusIs(sapi::StatusCode::kUnavailable)); + EXPECT_THAT(sandbox->AwaitResult().final_status(), + Eq(sandbox2::Result::VIOLATION)); + + // Restart the sandbox. + EXPECT_THAT(sandbox->Restart(false), IsOk()); + // The sandbox should now be responsive again. + auto result_or = sumapi.sum(1, 2); + EXPECT_THAT(result_or, IsOk()); + EXPECT_THAT(result_or.ValueOrDie(), Eq(3)); + return sapi::OkStatus(); + }; + + EXPECT_THAT(st.Run(test_body), IsOk()); +} + +TEST(SandboxTest, NoRaceInAwaitResult) { + auto sandbox = absl::make_unique(); + ASSERT_THAT(sandbox->Init(), IsOk()); + StringopApi api(sandbox.get()); + EXPECT_THAT(api.violate(), StatusIs(sapi::StatusCode::kUnavailable)); + absl::SleepFor(absl::Milliseconds(200)); // make sure we lose the race + const auto& result = sandbox->AwaitResult(); + EXPECT_THAT(result.final_status(), Eq(sandbox2::Result::VIOLATION)); +} + +} // namespace +} // namespace sapi diff --git a/sandboxed_api/tools/generator2/BUILD b/sandboxed_api/tools/generator2/BUILD new file mode 100644 index 0000000..b340022 --- /dev/null +++ b/sandboxed_api/tools/generator2/BUILD @@ -0,0 +1,91 @@ +# 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") + +py_library( + name = "code", + srcs = ["code.py"], +) + +py_test( + name = "code_test", + size = "small", + srcs = [ + "code_test.py", + "code_test_util.py", + ], + deps = [ + ":code", + "@com_google_absl_py//absl/testing:absltest", + "@com_google_absl_py//absl/testing:parameterized", + ], +) + +py_binary( + name = "sapi_generator", + srcs = ["sapi_generator.py"], + visibility = ["//visibility:public"], + deps = [ + ":code", + "@com_google_absl_py//absl:app", + ], +) + +cc_library( + name = "tests", + srcs = [ + "testdata/tests.cc", + "testdata/tests.h", + "testdata/tests2.cc", + ], + alwayslink = 1, +) + +# Targets for testing if generated code compiles +sapi_library( + name = "tests_sapi_generator", + embed = True, + input_files = [ + "testdata/tests.cc", + "testdata/tests2.cc", + ], + lib = ":tests", + lib_name = "Tests", + namespace = "sapi::tests", +) + +cc_binary( + name = "build_test_bin", + srcs = [ + "testdata/main.cc", + ":tests_sapi_generator.sapi.h", + ":tests_sapi_generator_embed.h", + ], + deps = [ + "//sandboxed_api:sapi", + "//sandboxed_api:vars", + "//sandboxed_api/sandbox2", + ], +) + +# Compilation test - always passes if dependency builds properly +sh_test( + name = "build_test", + srcs = ["build_test.sh"], + data = [":build_test_bin"], + tags = ["nobuilder"], +) diff --git a/sandboxed_api/tools/generator2/build_test.sh b/sandboxed_api/tools/generator2/build_test.sh new file mode 100755 index 0000000..a6fef1f --- /dev/null +++ b/sandboxed_api/tools/generator2/build_test.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# 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. + +# Code generation test. +# It fails whenever generated code of test dependencies does not compile. + +echo 'PASS' + diff --git a/sandboxed_api/tools/generator2/code.py b/sandboxed_api/tools/generator2/code.py new file mode 100644 index 0000000..ca1ec11 --- /dev/null +++ b/sandboxed_api/tools/generator2/code.py @@ -0,0 +1,912 @@ +# 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. + +"""Module related to code analysis and generation.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import itertools +import os +from clang import cindex + +# pylint: disable=unused-import +from typing import (Text, List, Optional, Set, Dict, Callable, IO, + Generator as Gen, Tuple, Union, Sequence) +# pylint: enable=unused-import + +_PARSE_OPTIONS = (cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES | + cindex.TranslationUnit.PARSE_INCOMPLETE | + # for include directives + cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD) + + +def get_header_guard(path): + # type: (Text) -> Text + """Generates header guard string from path.""" + # the output file will be most likely somewhere in genfiles, strip the + # prefix in that case, also strip .gen if this is a step before clang-format + if not path: + raise ValueError('Cannot prepare header guard from path: {}'.format(path)) + if 'genfiles/' in path: + path = path.split('genfiles/')[1] + if path.endswith('.gen'): + path = path.split('.gen')[0] + path = path.upper().replace('.', '_').replace('-', '_').replace('/', '_') + return path + '_' + + +def _stringify_tokens(tokens, separator='\n', callbacks=None): + # type: (Sequence[cindex.Token], Text, Dict[int, Callable]) -> Text + """Converts tokens to text respecting line position (disrespecting column).""" + previous = OutputLine(0, []) # not used in output + lines = [] # type: List[OutputLine] + + for _, group in itertools.groupby(tokens, lambda t: t.location.line): + group_list = list(group) + line = OutputLine(previous.next_tab, group_list) + + if callbacks and len(group_list) in callbacks: + callbacks[len(group_list)](group_list) + + lines.append(line) + previous = line + + return separator.join(str(l) for l in lines) + +TYPE_MAPPING = { + cindex.TypeKind.VOID: '::sapi::v::Void', + cindex.TypeKind.CHAR_S: '::sapi::v::Char', + cindex.TypeKind.INT: '::sapi::v::Int', + cindex.TypeKind.UINT: '::sapi::v::UInt', + cindex.TypeKind.LONG: '::sapi::v::Long', + cindex.TypeKind.ULONG: '::sapi::v::ULong', + cindex.TypeKind.UCHAR: '::sapi::v::UChar', + cindex.TypeKind.USHORT: '::sapi::v::UShort', + cindex.TypeKind.SHORT: '::sapi::v::Short', + cindex.TypeKind.LONGLONG: '::sapi::v::LLong', + cindex.TypeKind.ULONGLONG: '::sapi::v::ULLong', + cindex.TypeKind.FLOAT: '::sapi::v::Reg', + cindex.TypeKind.DOUBLE: '::sapi::v::Reg', + cindex.TypeKind.LONGDOUBLE: '::sapi::v::Reg', + cindex.TypeKind.SCHAR: '::sapi::v::SChar', + cindex.TypeKind.SHORT: '::sapi::v::Short', + cindex.TypeKind.BOOL: '::sapi::v::Bool', +} + + +class Type(object): + """Class representing a type. + + Wraps cindex.Type of the argument/return value and provides helpers for the + code generation. + """ + + def __init__(self, tu, clang_type): + # type: (_TranslationUnit, cindex.Type) -> None + self._clang_type = clang_type + self._tu = tu + + # pylint: disable=protected-access + def __eq__(self, other): + # type: (Type) -> bool + # Use get_usr() to deduplicate Type objects based on declaration + decl = self._get_declaration() + decl_o = other._get_declaration() + + return decl.get_usr() == decl_o.get_usr() + + def __ne__(self, other): + # type: (Type) -> bool + return not self.__eq__(other) + + def __lt__(self, other): + # type: (Type) -> bool + """Compares two Types belonging to the same TranslationUnit. + + This is being used to properly order types before emitting to generated + file. To be more specific: structure definition that contains field that is + a typedef should end up after that typedef definition. This is achieved by + exploiting the order in which clang iterate over AST in translation unit. + + Args: + other: other comparison type + + Returns: + true if this Type occurs earlier in the AST than 'other' + """ + self._validate_tu(other) + return (self._tu.order[self._get_declaration().hash] < + self._tu.order[other._get_declaration().hash]) # pylint: disable=protected-access + + def __gt__(self, other): + # type: (Type) -> bool + """Compares two Types belonging to the same TranslationUnit. + + This is being used to properly order types before emitting to generated + file. To be more specific: structure definition that contains field that is + a typedef should end up after that typedef definition. This is achieved by + exploiting the order in which clang iterate over AST in translation unit. + + Args: + other: other comparison type + + Returns: + true if this Type occurs later in the AST than 'other' + """ + self._validate_tu(other) + return (self._tu.order[self._get_declaration().hash] > + self._tu.order[other._get_declaration().hash]) # pylint: disable=protected-access + + def __hash__(self): + """Types with the same declaration should hash to the same value.""" + return hash(self._get_declaration().get_usr()) + + def _validate_tu(self, other): + # type: (Type) -> None + if self._tu != other._tu: # pylint: disable=protected-access + raise ValueError('Cannot compare types from different translation units.') + + def is_void(self): + # type: () -> bool + return self._clang_type.kind == cindex.TypeKind.VOID + + def is_typedef(self): + # type: () -> bool + return self._clang_type.kind == cindex.TypeKind.TYPEDEF + + # Hack: both class and struct types are indistinguishable except for + # declaration cursor kind + def is_elaborated(self): # class, struct, union + # type: () -> bool + return (self._clang_type.kind == cindex.TypeKind.ELABORATED or + self._clang_type.kind == cindex.TypeKind.RECORD) + + def is_struct(self): + # type: () -> bool + return (self.is_elaborated() and + self._get_declaration().kind == cindex.CursorKind.STRUCT_DECL) + + def is_class(self): + # type: () -> bool + return (self.is_elaborated() and + self._get_declaration().kind == cindex.CursorKind.CLASS_DECL) + + def is_function(self): + # type: () -> bool + return self._clang_type.kind == cindex.TypeKind.FUNCTIONPROTO + + def is_ptr(self): + # type: () -> bool + if self.is_typedef(): + return self._clang_type.get_canonical().kind == cindex.TypeKind.POINTER + return self._clang_type.kind == cindex.TypeKind.POINTER + + def is_enum(self): + # type: () -> bool + return self._clang_type.kind == cindex.TypeKind.ENUM + + def is_const_array(self): + # type: () -> bool + return self._clang_type.kind == cindex.TypeKind.CONSTANTARRAY + + def is_simple_type(self): + # type: () -> bool + return self._clang_type.kind in TYPE_MAPPING + + def get_pointee(self): + # type: () -> Type + return Type(self._tu, self._clang_type.get_pointee()) + + def _get_declaration(self): + # type: () -> cindex.Cursor + decl = self._clang_type.get_declaration() + if decl.kind == cindex.CursorKind.NO_DECL_FOUND and self.is_ptr(): + decl = self.get_pointee()._get_declaration() # pylint: disable=protected-access + + return decl + + def get_related_types(self, result=None, skip_self=False): + # type: (Set[Type], bool) -> Set[Type] + """Returns all types related to this one eg. typedefs, nested structs.""" + if result is None: + result = set() + + if self in result or self.is_simple_type() or self.is_class(): + return result + + if self.is_const_array(): + t = Type(self._tu, self._clang_type.get_array_element_type()) + return t.get_related_types(result) + + if self.is_typedef(): + return self._get_related_types_of_typedef(result) + + if self._clang_type.kind in (cindex.TypeKind.POINTER, + cindex.TypeKind.MEMBERPOINTER, + cindex.TypeKind.LVALUEREFERENCE, + cindex.TypeKind.RVALUEREFERENCE): + return self.get_pointee().get_related_types(result, skip_self) + + if self.is_elaborated(): # union + struct, class hould be filtered out + return self._get_related_types_of_elaborated(result, skip_self) + + if self.is_function(): + return self._get_related_types_of_function(result) + + if self.is_enum(): + if not skip_self: + result.add(self) + self._tu.search_for_macro_name(self._get_declaration()) + + return result + + raise ValueError('Unkhandled kind: {}'.format(self._clang_type.kind)) + + def _get_related_types_of_typedef(self, result): + # type: (Set[Type]) -> Set[Type] + """Returns all intermediate types related to the typedef.""" + result.add(self) + decl = self._get_declaration() + t = Type(self._tu, decl.underlying_typedef_type) + if t.is_ptr(): + t = t.get_pointee() + + self._tu.search_for_macro_name(decl) + + if not t.is_simple_type(): + skip_child = self.contains_declaration(t) + if t.is_elaborated() and skip_child: + # if child declaration is contained in parent, we don't have to emit it + self._tu.types_to_skip.add(t) + result.update(t.get_related_types(result, skip_child)) + + return result + + def _get_related_types_of_elaborated(self, result, skip_self=False): + # type: (Set[Type], bool) -> Set[Type] + """Returns all types related to the structure.""" + # skip unnamed structures eg. typedef struct {...} x; + # struct {...} will be rendered as part of typedef rendering + if self._get_declaration().spelling and not skip_self: + result.add(self) + + for f in self._clang_type.get_fields(): + self._tu.search_for_macro_name(f) + result.update(Type(self._tu, f.type).get_related_types(result)) + + return result + + def _get_related_types_of_function(self, result): + # type: (Set[Type]) -> Set[Type] + """Returns all types related to the function.""" + for arg in self._clang_type.argument_types(): + result.update(Type(self._tu, arg).get_related_types(result)) + related = Type(self._tu, + self._clang_type.get_result()).get_related_types(result) + result.update(related) + + return result + + def contains_declaration(self, other): + # type: (Type) -> bool + """Checks if string representation of a type contains the other type.""" + self_extent = self._get_declaration().extent + other_extent = other._get_declaration().extent # pylint: disable=protected-access + + if other_extent.start.file is None: + return False + return (other_extent.start in self_extent and + other_extent.end in self_extent) + + def stringify(self): + # type: () -> Text + """Returns string representation of the Type.""" + # (szwl): as simple as possible, keeps macros in separate lines not to + # break things; this will go through clang format nevertheless + tokens = [x for x in self._get_declaration().get_tokens() + if x.kind is not cindex.TokenKind.COMMENT] + # look for lines with two tokens: a way of finding structures with + # body as a macro eg: + # #define BODY \ + # int a; \ + # int b; + # struct test { + # BODY; + # } + callbacks = {} + callbacks[2] = lambda x: self._tu.required_defines.add(x[0].spelling) + return _stringify_tokens(tokens, callbacks=callbacks) + + +class OutputLine(object): + """Helper class for Type printing.""" + + def __init__(self, tab, tokens): + # type: (int, List[cindex.Token]) -> None + self.tokens = tokens + self.define = False + self.tab = tab + self.next_tab = tab + map(self._process_token, self.tokens) + + def append(self, t): + # type: (cindex.Token) -> None + """Appends token to the line.""" + self._process_token(t) + self.tokens.append(t) + + def _process_token(self, t): + # type: (cindex.Token) -> None + if t.spelling == '#': + self.define = True + elif t.spelling == '{': + self.next_tab += 1 + elif t.spelling == '}': + self.tab -= 1 + self.next_tab -= 1 + + def __str__(self): + # type: () -> Text + tabs = ('\t'*self.tab) if not self.define else '' + + return tabs + ' '.join(t.spelling for t in self.tokens) + + +class ArgumentType(Type): + """Class representing function argument type. + + Object fields are being used by the code template: + pos: argument position + type: string representation of the type + argument: string representation of the type as function argument + mapped_type: SAPI equivalent of the type + wrapped: wraps type in SAPI object constructor + call_argument: type (or it's sapi wrapper) used in function call + """ + + def __init__(self, function, pos, arg_type, name=None): + # type: (Function, int, cindex.Type, Text) -> None + super(ArgumentType, self).__init__(function.translation_unit(), arg_type) + self._function = function + + self.pos = pos + self.name = name or 'a{}'.format(pos) + self.type = arg_type.spelling + + template = '{}' if self.is_ptr() else '&{}_' + self.call_argument = template.format(self.name) + + def __str__(self): + # type: () -> Text + """Returns function argument prepared from the type.""" + if self.is_ptr(): + return '::sapi::v::Ptr* {}'.format(self.name) + + return '{} {}'.format(self._clang_type.spelling, self.name) + + @property + def wrapped(self): + # type: () -> Text + return '{} {name}_(({name}))'.format(self.mapped_type, name=self.name) + + @property + def mapped_type(self): + # type: () -> Text + """Maps the type to its SAPI equivalent.""" + if self.is_ptr(): + # TODO(szwl): const ptrs do not play well with SAPI C++ API... + spelling = self._clang_type.spelling.replace('const', '') + return '::sapi::v::Reg<{}>'.format(spelling) + + type_ = self._clang_type + + if type_.kind == cindex.TypeKind.TYPEDEF: + type_ = self._clang_type.get_canonical() + if type_.kind == cindex.TypeKind.ELABORATED: + type_ = type_.get_canonical() + if type_.kind == cindex.TypeKind.ENUM: + return '::sapi::v::IntBase<{}>'.format(self._clang_type.spelling) + if type_.kind in [cindex.TypeKind.CONSTANTARRAY, + cindex.TypeKind.INCOMPLETEARRAY]: + return '::sapi::v::Reg<{}>'.format(self._clang_type.spelling) + + if type_.kind == cindex.TypeKind.LVALUEREFERENCE: + return 'LVALUEREFERENCE::NOT_SUPPORTED' + + if type_.kind == cindex.TypeKind.RVALUEREFERENCE: + return 'RVALUEREFERENCE::NOT_SUPPORTED' + + if type_.kind in [cindex.TypeKind.RECORD, cindex.TypeKind.ELABORATED]: + raise ValueError('Elaborate type (eg. struct) in mapped_type is not ' + 'supported: function {}, arg {}, type {}, location {}' + ''.format(self._function.name, self.pos, + self._clang_type.spelling, + self._function.cursor.location)) + + if type_.kind not in TYPE_MAPPING: + raise KeyError('Key {} does not exist in TYPE_MAPPING.' + ' function {}, arg {}, type {}, location {}' + ''.format(type_.kind, self._function.name, self.pos, + self._clang_type.spelling, + self._function.cursor.location)) + + return TYPE_MAPPING[type_.kind] + + +class ReturnType(ArgumentType): + """Class representing function return type. + + Attributes: + return_type: sapi::StatusOr where T is original return type, or + sapi::Status for functions returning void + """ + + def __init__(self, function, arg_type): + # type: (Function, cindex.Type) -> None + super(ReturnType, self).__init__(function, 0, arg_type, None) + + def __str__(self): + # type: () -> Text + """Returns function return type prepared from the type.""" + # TODO(szwl): const ptrs do not play well with SAPI C++ API... + spelling = self._clang_type.spelling.replace('const', '') + return_type = 'sapi::StatusOr<{}>'.format(spelling) + return_type = 'sapi::Status' if self.is_void() else return_type + return return_type + + +class Function(object): + """Class representing SAPI-wrapped function used by the template. + + Wraps Clang cursor object of kind FUNCTION_DECL and provides helpers to + aid code generation. + """ + + def __init__(self, tu, cursor): + # type: (_TranslationUnit, cindex.Cursor) -> None + self._tu = tu + self.cursor = cursor # type: cindex.Index + self.name = cursor.spelling # type: Text + self.mangled_name = cursor.mangled_name # type: Text + self.result = ReturnType(self, cursor.result_type) + self.original_definition = '{} {}'.format(cursor.result_type.spelling, + self.cursor.displayname) # type: Text + + types = self.cursor.get_arguments() + self.argument_types = [ArgumentType(self, i, t.type, t.spelling) for i, t + in enumerate(types)] + + def translation_unit(self): + # type: () -> _TranslationUnit + return self._tu + + def arguments(self): + # type: () -> List[ArgumentType] + return self.argument_types + + def call_arguments(self): + # type: () -> List[Text] + return [a.call_argument for a in self.argument_types] + + def get_absolute_path(self): + # type: () -> Text + return self.cursor.location.file.name + + def get_include_path(self, prefix): + # type: (Optional[Text]) -> Text + """Creates a proper include path.""" + # TODO(szwl): sanity checks + # TODO(szwl): prefix 'utils/' and the path is '.../fileutils/...' case + if prefix and not prefix.endswith('/'): + prefix += '/' + + if not prefix: + return self.get_absolute_path() + elif prefix in self.get_absolute_path(): + return prefix + self.get_absolute_path().split(prefix)[-1] + return prefix + self.get_absolute_path().split('/')[-1] + + def get_related_types(self, processed=None): + # type: (Set[Type]) -> Set[Type] + result = self.result.get_related_types(processed) + for a in self.argument_types: + result.update(a.get_related_types(processed)) + + return result + + def is_mangled(self): + # type: () -> bool + return self.name != self.mangled_name + + def __hash__(self): + # type: () -> int + return hash(self.cursor.get_usr()) + + def __eq__(self, other): + # type: (Function) -> bool + return self.mangled_name == other.mangled_name + + +class _TranslationUnit(object): + """Class wrapping clang's _TranslationUnit. Provides extra utilities.""" + + def __init__(self, tu): + # type: (cindex.TranslatioUnit) -> None + self._tu = tu + self._processed = False + self.forward_decls = dict() + self.functions = set() + self.order = dict() + self.defines = {} + self.required_defines = set() + self.types_to_skip = set() + + def _process(self): + # type: () -> None + """Walks the cursor tree and caches some for future use.""" + if not self._processed: + # self.includes[self._tu.spelling] = (0, self._tu.cursor) + self._processed = True + # TODO(szwl): duplicates? + # TODO(szwl): for d in translation_unit.diagnostics:, handle that + + for i, cursor in enumerate(self._walk_preorder()): + # naive way to order types: they should be ordered when walking the tree + if cursor.kind.is_declaration(): + self.order[cursor.hash] = i + + if (cursor.kind == cindex.CursorKind.MACRO_DEFINITION and + cursor.location.file): + self.defines[cursor.spelling] = cursor + + # most likely a forward decl of struct + if (cursor.kind == cindex.CursorKind.STRUCT_DECL and + not cursor.is_definition()): + self.forward_decls[Type(self, cursor.type)] = cursor + + if (cursor.kind == cindex.CursorKind.FUNCTION_DECL and + cursor.linkage != cindex.LinkageKind.INTERNAL): + self.functions.add(Function(self, cursor)) + + def get_functions(self): + # type: () -> Set[Function] + if not self._processed: + self._process() + return self.functions + + def _walk_preorder(self): + # type: () -> Gen + for c in self._tu.cursor.walk_preorder(): + yield c + + # TODO(szwl): expand to look for macros in structs, unions etc. + def search_for_macro_name(self, cursor): + # type: (cindex.Cursor) -> None + """Searches for possible macro usage in constant array types.""" + tokens = list(t.spelling for t in cursor.get_tokens()) + try: + for token in tokens: + if token in self.defines: + self.required_defines.add(token) + except ValueError: + return + + +class Analyzer(object): + """Class responsible for analysis.""" + + @staticmethod + def process_files(input_paths, compile_flags): + # type: (Text, List[Text]) -> List[_TranslationUnit] + return [Analyzer._analyze_file_for_tu(path, compile_flags=compile_flags) + for path in input_paths] + + # pylint: disable=line-too-long + @staticmethod + def _analyze_file_for_tu(path, + compile_flags=None, + test_file_existence=True, + unsaved_files=None + ): + # type: (Text, List[Text], bool, Tuple[Text, Union[Text, IO[Text]]]) -> _TranslationUnit + """Returns Analysis object for given path.""" + compile_flags = compile_flags or [] + if test_file_existence and not os.path.isfile(path): + raise IOError('Path {} does not exist.'.format(path)) + + index = cindex.Index.create() # type: cindex.Index + # TODO(szwl): hack until I figure out how python swig does that. + # Headers will be parsed as C++. C libs usually have + # '#ifdef __cplusplus extern "C"' for compatibility with c++ + lang = '-xc++' if not path.endswith('.c') else '-xc' + args = [lang] + args += compile_flags + args.append('-I.') + return _TranslationUnit(index.parse(path, args=args, + unsaved_files=unsaved_files, + options=_PARSE_OPTIONS)) + + +class Generator(object): + """Class responsible for code generation.""" + + AUTO_GENERATED = ('// AUTO-GENERATED by the Sandboxed API generator.\n' + '// Edits will be discarded when regenerating this file.\n') + + GUARD_START = ('#ifndef {0}\n' + '#define {0}') + GUARD_END = '#endif // {}' + EMBED_INCLUDE = '#include \"{}/{}_embed.h"' + EMBED_CLASS = ('class {0}Sandbox : public ::sapi::Sandbox {{\n' + ' public:\n' + ' {0}Sandbox() : ::sapi::Sandbox({1}_embed_create()) {{}}\n' + '}};') + + def __init__(self, translation_units): + # type: (List[cindex.TranslationUnit]) -> None + """Initialize Generator. + + Args: + translation_units: list of translation_units for analyzed files, + facultative. If not given, then one is computed for each element of + input_paths + """ + + self.translation_units = translation_units + self.functions = None + + def generate(self, name, function_names, namespace=None, output_file=None, + embed_dir=None, embed_name=None): + # type: (Text, List[Text], Text, Text, Text, Text) -> Text + """Generates structures, functions and typedefs. + + Args: + name: name of the class that will contain generated interface + function_names: list of function names to export to the interface + namespace: namespace of the interface + output_file: path to the output file, used to generate header guards; + defaults to None that does not generate the guard + #include directives; defaults to None that causes to emit the whole file + path + embed_dir: path to directory with embed includes + embed_name: name of the embed object + Returns: + generated interface as a string + """ + related_types = self._get_related_types(function_names) + forward_decls = self._get_forward_decls(related_types) + functions = self._get_functions(function_names) + related_types = [(t.stringify() + ';') for t in related_types] + defines = self._get_defines() + + api = { + 'name': name, + 'functions': functions, + 'related_types': defines + forward_decls + related_types, + 'namespaces': namespace.split('::') if namespace else [], + 'embed_dir': embed_dir, + 'embed_name': embed_name, + 'output_file': output_file + } + return self.format_template(**api) + + def _get_functions(self, func_names=None): + # type: (List[Text]) -> List[Function] + """Gets Function objects that will be used to generate interface.""" + if self.functions is not None: + return self.functions + self.functions = [] + # TODO(szwl): for d in translation_unit.diagnostics:, handle that + for translation_unit in self.translation_units: + self.functions += [f for f in translation_unit.get_functions() + if not func_names or f.name in func_names] + # allow only nonmangled functions - C++ overloads are not handled in + # code generation + self.functions = [f for f in self.functions if not f.is_mangled()] + + # remove duplicates + self.functions = list(set(self.functions)) + self.functions.sort(key=lambda x: x.name) + return self.functions + + def _get_related_types(self, func_names=None): + # type: (List[Text]) -> List[Type] + """Gets type definitions related to chosen functions. + + Types related to one function will land in the same translation unit, + we gather the types, sort it and put as a sublist in types list. + This is necessary as we can't compare types from two different translation + units. + + Args: + func_names: list of function names to take into consideration, empty means + all functions. + + Returns: + list of types in correct (ready to render) order + """ + processed = set() + fn_related_types = set() + types = [] + types_to_skip = set() + + for f in self._get_functions(func_names): + fn_related_types = f.get_related_types() + types += sorted(r for r in fn_related_types if r not in processed) + processed.update(fn_related_types) + types_to_skip.update(f.translation_unit().types_to_skip) + + return [t for t in types if t not in types_to_skip] + + def _get_defines(self): + # type: () -> List[Text] + """Gets #define directives that appeared during TranslationUnit processing. + + Returns: + list of #define string representations + """ + result = [] + for tu in self.translation_units: + for name in tu.required_defines: + if name in tu.defines: + define = tu.defines[name] + result.append('#define ' + _stringify_tokens(define.get_tokens(), + separator=' \\\n')) + return result + + def _get_forward_decls(self, types): + # type: (List[Type]) -> List[Text] + """Gets forward declarations of related types, if present.""" + forward_decls = dict() + result = [] + done = set() + for tu in self.translation_units: + forward_decls.update(tu.forward_decls) + + for t in types: + if t in forward_decls and t not in done: + result.append(_stringify_tokens(forward_decls[t].get_tokens()) + ';') + done.add(t) + + return result + + def _format_function(self, f): + # type: (Function) -> Text + """Renders one function of the Api. + + Args: + f: function object with information necessary to emit full function body + + Returns: + filled function template + """ + result = [] + result.append(' // {}'.format(f.original_definition)) + + arguments = ', '.join(str(a) for a in f.arguments()) + result.append(' {} {}({}) {{'.format(f.result, f.name, arguments)) + result.append(' {} ret;'.format(f.result.mapped_type)) + + argument_types = [] + for a in f.argument_types: + if not a.is_ptr(): + argument_types.append(a.wrapped + ';') + if argument_types: + for arg in argument_types: + result.append(' {}'.format(arg)) + + call_arguments = f.call_arguments() + if call_arguments: # fake empty space to add ',' before first argument + call_arguments.insert(0, '') + result.append('') + # In OSS code, the code below uses SAPI_RETURN_IF_ERROR(). + result.append(' SAPI_RETURN_IF_ERROR(sandbox_->Call("{}", &ret{}));' + ''.format(f.name, ', '.join(call_arguments))) + + return_status = 'return sapi::OkStatus();' + if f.result and not f.result.is_void(): + if f.result and f.result.is_enum(): + return_status = ('return static_cast<{}>' + '(ret.GetValue());').format(f.result.type) + else: + return_status = 'return ret.GetValue();' + result.append(' {}'.format(return_status)) + result.append(' }') + + return '\n'.join(result) + + def format_template(self, name, functions, related_types, namespaces, + embed_dir, embed_name, output_file): + # pylint: disable=line-too-long + # type: (Text, List[Function], List[Type], List[Text], Text, Text, Text) -> Text + # pylint: enable=line-too-long + """Formats arguments into proper interface header file. + + Args: + name: name of the Api - 'Test' will yield TestApi object + functions: list of functions to generate + related_types: types used in the above functions + namespaces: list of namespaces to wrap the Api class with + embed_dir: directory where the embedded library lives + embed_name: name of embedded library + output_file: interface output path - used in header guard generation + + Returns: + generated header file text + """ + result = [Generator.AUTO_GENERATED] + + header_guard = get_header_guard(output_file) if output_file else '' + if header_guard: + result.append(Generator.GUARD_START.format(header_guard)) + + # Copybara transform results in the paths below. + result.append('#include "sandboxed_api/sandbox.h"') + result.append('#include "sandboxed_api/vars.h"') + + if embed_dir and embed_name: + result.append(Generator.EMBED_INCLUDE.format(embed_dir, embed_name)) + + result.append('') + if namespaces: + for n in namespaces: + result.append('namespace {} {{'.format(n)) + + result.append('') + result.append('') + + if related_types: + for t in related_types: + result.append(t) + + result.append('') + result.append('') + + if embed_dir and embed_name: + result.append( + Generator.EMBED_CLASS.format(name, embed_name.replace('-', '_'))) + + result.append('class {}Api {{'.format(name)) + result.append(' public:') + result.append(' explicit {}Api(::sapi::Sandbox* sandbox)' + ' : sandbox_(sandbox) {{}}'.format(name)) + result.append(' ::sapi::Sandbox* GetSandbox() {') + result.append(' return sandbox_;') + result.append(' }') + result.append('') + result.append('') + + for f in functions: + result.append(self._format_function(f)) + result.append('') + + result.append('') + result.append(' private:') + result.append(' ::sapi::Sandbox* sandbox_;') + result.append('};') + result.append('') + + if namespaces: + for n in reversed(namespaces): + result.append('}} // namespace {}'.format(n)) + + if header_guard: + result.append(Generator.GUARD_END.format(header_guard)) + + result.append('') + result.append('') + result.append('') + + return '\n'.join(result) diff --git a/sandboxed_api/tools/generator2/code_test.py b/sandboxed_api/tools/generator2/code_test.py new file mode 100644 index 0000000..7b534df --- /dev/null +++ b/sandboxed_api/tools/generator2/code_test.py @@ -0,0 +1,786 @@ +# 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. + +"""Tests for google3.third_party.sandboxed_api.tools.generator2.code.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from absl.testing import absltest +from absl.testing import parameterized +from clang import cindex +import code +import code_test_util + + +CODE = """ +typedef int(fun*)(int,int); +extern "C" int function_a(int x, int y) { return x + y; } +extern "C" int function_b(int a, int b) { return a + b; } + +struct a { + void (*fun_ptr)(char, long); +} +""" + + +def analyze_string(content, path='tmp.cc'): + """Returns Analysis object for in memory content.""" + return analyze_strings(path, [(path, content)]) + + +def analyze_strings(path, unsaved_files): + """Returns Analysis object for in memory content.""" + return code.Analyzer._analyze_file_for_tu(path, None, False, unsaved_files) + + +class CodeAnalysisTest(parameterized.TestCase): + + def testInMemoryFile(self): + translation_unit = analyze_string(CODE) + self.assertIsNotNone(translation_unit._tu.cursor) + + def testSimpleASTTraversal(self): + translation_unit = analyze_string(CODE) + + structs = 0 + functions = 0 + params = 0 + typedefs = 0 + + for cursor in translation_unit._walk_preorder(): + if cursor.kind == cindex.CursorKind.FUNCTION_DECL: + functions += 1 + elif cursor.kind == cindex.CursorKind.STRUCT_DECL: + structs += 1 + elif cursor.kind == cindex.CursorKind.PARM_DECL: + params += 1 + elif cursor.kind == cindex.CursorKind.TYPEDEF_DECL: + typedefs += 1 + + self.assertEqual(functions, 2) + self.assertEqual(structs, 1) + self.assertEqual(params, 8) + self.assertEqual(typedefs, 1) + + def testParseSkipFunctionBodies(self): + function_body = 'extern "C" int function(bool a1) { return a1 ? 1 : 2; }' + translation_unit = analyze_string(function_body) + for cursor in translation_unit._walk_preorder(): + if cursor.kind == cindex.CursorKind.FUNCTION_DECL: + # cursor.get_definition() is None when we skip parsing function bodies + self.assertIsNone(cursor.get_definition()) + + def testExternC(self): + translation_unit = analyze_string('extern "C" int function(char* a);') + cursor_kinds = [x.kind for x in translation_unit._walk_preorder() + if x.kind != cindex.CursorKind.MACRO_DEFINITION] + self.assertListEqual(cursor_kinds, [cindex.CursorKind.TRANSLATION_UNIT, + cindex.CursorKind.UNEXPOSED_DECL, + cindex.CursorKind.FUNCTION_DECL, + cindex.CursorKind.PARM_DECL]) + @parameterized.named_parameters( + ('1:', '/tmp/test.h', 'tmp', 'tmp/test.h'), + ('2:', '/a/b/c/d/tmp/test.h', 'c/d', 'c/d/tmp/test.h'), + ('3:', '/tmp/test.h', None, '/tmp/test.h'), + ('4:', '/tmp/test.h', '', '/tmp/test.h'), + ('5:', '/tmp/test.h', 'xxx', 'xxx/test.h'), + ) + + def testGetIncludes(self, path, prefix, expected): + function_body = 'extern "C" int function(bool a1) { return a1 ? 1 : 2; }' + translation_unit = analyze_string(function_body) + for cursor in translation_unit._walk_preorder(): + if cursor.kind == cindex.CursorKind.FUNCTION_DECL: + fn = code.Function(translation_unit, cursor) + fn.get_absolute_path = lambda: path + self.assertEqual(fn.get_include_path(prefix), expected) + + def testCodeGeneratorOutput(self): + body = """ + extern "C" { + int function_a(int x, int y) { return x + y; } + + int types_1(bool a0, unsigned char a1, char a2, unsigned short a3, short a4); + int types_2(int a0, unsigned int a1, long a2, unsigned long a3); + int types_3(long long a0, unsigned long long a1, float a2, double a3); + int types_4(signed char a0, signed short a1, signed int a2, signed long a3); + int types_5(signed long long a0, long double a1); + void types_6(char* a0); + } + """ + functions = ['function_a', 'types_1', 'types_2', 'types_3', 'types_4', + 'types_5', 'types_6'] + generator = code.Generator([analyze_string(body)]) + result = generator.generate('Test', functions, 'sapi::Tests', None, None) + self.assertMultiLineEqual(code_test_util.CODE_GOLD, result) + + def testElaboratedArgument(self): + body = """ + struct x { int a; }; + extern "C" int function(struct x a) { return a.a; } + """ + generator = code.Generator([analyze_string(body)]) + with self.assertRaisesRegexp(ValueError, r'Elaborate.*mapped.*'): + generator.generate('Test', ['function'], 'sapi::Tests', None, None) + + def testElaboratedArgument2(self): + body = """ + typedef struct { int a; char b; } x; + extern "C" int function(x a) { return a.a; } + """ + generator = code.Generator([analyze_string(body)]) + with self.assertRaisesRegexp(ValueError, r'Elaborate.*mapped.*'): + generator.generate('Test', ['function'], 'sapi::Tests', None, None) + + def testGetMappedType(self): + body = """ + typedef unsigned int uint; + typedef uint* uintp; + extern "C" uint function(uintp a) { return *a; } + """ + generator = code.Generator([analyze_string(body)]) + result = generator.generate('Test', [], 'sapi::Tests', None, None) + self.assertMultiLineEqual(code_test_util.CODE_GOLD_MAPPED, result) + + @parameterized.named_parameters( + ('1:', '/tmp/test.h', '_TMP_TEST_H_'), + ('2:', 'tmp/te-st.h', 'TMP_TE_ST_H_'), + ('3:', 'tmp/te-st.h.gen', 'TMP_TE_ST_H_'), + ('4:', 'xx/genfiles/tmp/te-st.h', 'TMP_TE_ST_H_'), + ('5:', 'xx/genfiles/tmp/te-st.h.gen', 'TMP_TE_ST_H_'), + ('6:', 'xx/genfiles/.gen/tmp/te-st.h', '_GEN_TMP_TE_ST_H_'), + ) + def testGetHeaderGuard(self, path, expected): + self.assertEqual(code.get_header_guard(path), expected) + + @parameterized.named_parameters( + ('function with return value and arguments', + 'extern "C" int function(bool arg_bool, char* arg_ptr);', + ['arg_bool', 'arg_ptr']), + ('function without return value and no arguments', + 'extern "C" void function();', + []), + ) + def testArgumentNames(self, body, names): + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + self.assertLen(functions[0].argument_types, len(names)) + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + for t in functions[0].argument_types: + self.assertIn(t.name, names) + + def testStaticFunctions(self): + body = 'static int function() { return 7; };' + generator = code.Generator([analyze_string(body)]) + self.assertEmpty(generator._get_functions()) + + def testEnumGeneration(self): + body = """ + enum ProcessStatus { + OK = 0, + ERROR = 1, + }; + + extern "C" ProcessStatus ProcessDatapoint(ProcessStatus status) { + return status; + } + """ + generator = code.Generator([analyze_string(body)]) + result = generator.generate('Test', [], 'sapi::Tests', None, None) + self.assertMultiLineEqual(code_test_util.CODE_ENUM_GOLD, result) + + def testTypeEq(self): + body = """ + typedef unsigned int uint; + extern "C" void function(uint a1, uint a2, char a3); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 3) + self.assertEqual(args[0], args[1]) + self.assertNotEqual(args[0], args[2]) + self.assertNotEqual(args[1], args[2]) + + self.assertLen(set(args), 2) + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testTypedefRelatedTypes(self): + body = """ + typedef unsigned int uint; + typedef uint* uint_p; + typedef uint_p* uint_pp; + + typedef struct data { + int a; + int b; + } data_s; + typedef data_s* data_p; + + extern "C" uint function_using_typedefs(uint_p a1, uint_pp a2, data_p a3); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 3) + + types = args[0].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 2) + self.assertSameElements(names, ['uint_p', 'uint']) + + types = args[1].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 3) + self.assertSameElements(names, ['uint_pp', 'uint_p', 'uint']) + + types = args[2].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 2) + self.assertSameElements(names, ['data_s', 'data_p']) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testTypedefDuplicateType(self): + body = """ + typedef struct data { + int a; + int b; + } data_s; + + struct s { + struct data* f1; + }; + + extern "C" uint function_using_typedefs(struct s* a1, data_s* a2); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 2) + + types = generator._get_related_types() + self.assertLen(generator.translation_units[0].types_to_skip, 1) + + names = [t._clang_type.spelling for t in types] + self.assertSameElements(['data_s', 'struct s'], names) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testStructureRelatedTypes(self): + body = """ + typedef unsigned int uint; + + typedef struct { + uint a; + struct { + int a; + int b; + } b; + } struct_1; + + struct struct_2 { + uint a; + char b; + struct_1* c; + }; + + typedef struct a { + int b; + } struct_a; + + extern "C" int function_using_structures(struct struct_2* a1, struct_1* a2, + struct_a* a3); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 3) + + types = args[0].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 3) + self.assertSameElements(names, ['struct struct_2', 'uint', 'struct_1']) + + types = args[1].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 2) + self.assertSameElements(names, ['struct_1', 'uint']) + + names = [t._clang_type.spelling for t in generator._get_related_types()] + self.assertEqual(names, ['uint', 'struct_1', 'struct struct_2', 'struct_a']) + + types = args[2].get_related_types() + self.assertLen(types, 1) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testUnionRelatedTypes(self): + body = """ + typedef unsigned int uint; + + typedef union { + uint a; + union { + int a; + int b; + } b; + } union_1; + + union union_2 { + uint a; + char b; + union_1* c; + }; + + extern "C" int function_using_unions(union union_2* a1, union_1* a2); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 2) + + types = args[0].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 3) + self.assertSameElements(names, ['union union_2', 'uint', 'union_1']) + + types = args[1].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 2) + self.assertSameElements(names, ['union_1', 'uint']) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testFunctionPointerRelatedTypes(self): + body = """ + typedef unsigned int uint; + typedef unsigned char uchar; + typedef uint (*funcp)(uchar, uchar); + + struct struct_1 { + uint (*func)(uchar); + int a; + }; + + extern "C" void function(struct struct_1* a1, funcp a2); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 2) + + types = args[0].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 3) + self.assertSameElements(names, ['struct struct_1', 'uint', 'uchar']) + + types = args[1].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 3) + self.assertSameElements(names, ['funcp', 'uint', 'uchar']) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testForwardDeclaration(self): + body = """ + struct struct_6_def; + typedef struct struct_6_def struct_6; + typedef struct_6* struct_6p; + typedef void (*function_p3)(struct_6p); + struct struct_6_def { + function_p3 fn; + }; + + extern "C" void function_using_type_loop(struct_6p a1); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 1) + + types = args[0].get_related_types() + names = [t._clang_type.spelling for t in types] + self.assertLen(types, 4) + self.assertSameElements(names, ['struct_6p', 'struct_6', + 'struct struct_6_def', 'function_p3']) + + self.assertLen(generator.translation_units, 1) + self.assertLen(generator.translation_units[0].forward_decls, 1) + + t = next(x for x in types + if x._clang_type.spelling == 'struct struct_6_def') + self.assertIn(t, generator.translation_units[0].forward_decls) + + names = [t._clang_type.spelling for t in generator._get_related_types()] + self.assertEqual(names, ['struct_6', 'struct_6p', + 'function_p3', 'struct struct_6_def']) + + # Extra check for generation, in case rendering throws error for this test. + forward_decls = generator._get_forward_decls(generator._get_related_types()) + self.assertLen(forward_decls, 1) + self.assertEqual(forward_decls[0], 'struct struct_6_def;') + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testEnumRelatedTypes(self): + body = """ + enum Enumeration { ONE, TWO, THREE }; + typedef enum Numbers { UNKNOWN, FIVE = 5, SE7EN = 7 } Nums; + typedef enum { SIX = 6, TEN = 10 } SixOrTen; + enum class Color : long long { RED, GREEN = 20, BLUE }; // NOLINT + enum struct Direction { LEFT = 'l', RIGHT = 'r' }; + enum __rlimit_resource { RLIMIT_CPU = 0, RLIMIT_MEM = 1}; + + extern "C" int function_using_enums(Enumeration a1, SixOrTen a2, Color a3, + Direction a4, Nums a5, enum __rlimit_resource a6); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 6) + + self.assertLen(args[0].get_related_types(), 1) + self.assertLen(args[1].get_related_types(), 1) + self.assertLen(args[2].get_related_types(), 1) + self.assertLen(args[3].get_related_types(), 1) + self.assertLen(args[4].get_related_types(), 1) + self.assertLen(args[5].get_related_types(), 1) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testArrayAsParam(self): + body = """ + extern "C" int function_using_enums(char a[10], char *const __argv[]); + """ + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + self.assertLen(args, 2) + + @parameterized.named_parameters( + ('uint < ushort ', 'assertLess', 1, 2), + ('uint < chr ', 'assertLess', 1, 3), + ('uint < uchar ', 'assertLess', 1, 4), + ('uint < u32 ', 'assertLess', 1, 5), + ('uint < ulong ', 'assertLess', 1, 6), + ('ushort < chr ', 'assertLess', 2, 3), + ('ushort < uchar ', 'assertLess', 2, 4), + ('ushort < u32 ', 'assertLess', 2, 5), + ('ushort < ulong ', 'assertLess', 2, 6), + ('chr < uchar ', 'assertLess', 3, 4), + ('chr < u32 ', 'assertLess', 3, 5), + ('chr < ulong ', 'assertLess', 3, 6), + ('uchar < u32 ', 'assertLess', 4, 5), + ('uchar < ulong ', 'assertLess', 4, 6), + ('u32 < ulong ', 'assertLess', 5, 6), + ('ushort > uint ', 'assertGreater', 2, 1), + ('chr > uint ', 'assertGreater', 3, 1), + ('uchar > uint ', 'assertGreater', 4, 1), + ('u32 > uint ', 'assertGreater', 5, 1), + ('ulong > uint ', 'assertGreater', 6, 1), + ('chr > ushort ', 'assertGreater', 3, 2), + ('uchar > ushort ', 'assertGreater', 4, 2), + ('u32 > ushort ', 'assertGreater', 5, 2), + ('ulong > ushort ', 'assertGreater', 6, 2), + ('uchar > chr ', 'assertGreater', 4, 3), + ('u32 > chr ', 'assertGreater', 5, 3), + ('ulong > chr ', 'assertGreater', 6, 3), + ('u32 > uchar ', 'assertGreater', 5, 4), + ('ulong > uchar ', 'assertGreater', 6, 4), + ('ulong > u32 ', 'assertGreater', 6, 5), + ) + def testTypeOrder(self, func, a1, a2): + """Checks if comparison functions of Type class work properly. + + This is necessary for Generator._get_related_types to return types in + proper order, ready to be emitted in the generated file. To be more + specific: emitted types will be ordered in a way that would allow + compilation ie. if structure field type is a typedef, typedef definition + will end up before structure definition. + + Args: + func: comparison assert to call + a1: function argument number to take the type to compare + a2: function argument number to take the type to compare + """ + + file1_code = """ + typedef unsigned int uint; + #include "/f2.h" + typedef uint u32; + #include "/f3.h" + + struct args { + u32 a; + uchar b; + ulong c; + ushort d; + chr e; + }; + extern "C" int function(struct args* a0, uint a1, ushort a2, chr a3, + uchar a4, u32 a5, ulong a6, struct args* a7); + """ + file2_code = """ + typedef unsigned short ushort; + #include "/f4.h" + typedef unsigned char uchar;""" + file3_code = 'typedef unsigned long ulong;' + file4_code = 'typedef char chr;' + files = [('f1.h', file1_code), ('/f2.h', file2_code), + ('/f3.h', file3_code), ('/f4.h', file4_code)] + generator = code.Generator([analyze_strings('f1.h', files)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + args = functions[0].arguments() + getattr(self, func)(args[a1], args[a2]) + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testTypeToString(self): + body = """ + #define SIZE 1024 + typedef unsigned int uint; + + typedef struct { + #if SOME_DEFINE >= 12 \ + && SOME_OTHER == 13 + uint a; + #else + uint aa; + #endif + struct { + uint a; + int b; + char c[SIZE]; + } b; + } struct_1; + + extern "C" int function_using_structures(struct_1* a1); + """ + + # pylint: disable=trailing-whitespace + expected = """typedef struct { +# if SOME_DEFINE >= 12 && SOME_OTHER == 13 +\tuint a ; +# else +\tuint aa ; +# endif +\tstruct { +\t\tuint a ; +\t\tint b ; +\t\tchar c [ SIZE ] ; +\t} b ; +} struct_1""" + generator = code.Generator([analyze_string(body)]) + functions = generator._get_functions() + self.assertLen(functions, 1) + + types = generator._get_related_types() + self.assertLen(types, 2) + self.assertEqual('typedef unsigned int uint', types[0].stringify()) + self.assertMultiLineEqual(expected, types[1].stringify()) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testCollectDefines(self): + body = """ + #define SIZE 1024 + #define NOT_USED 7 + #define SIZE2 2*1024 + #define SIZE3 1337 + #define SIZE4 10 + struct test { + int a[SIZE]; + char b[SIZE2]; + float c[777]; + int (*d)[SIZE3*SIZE4]; + }; + extern "C" int function_1(struct test* a1); + """ + generator = code.Generator([analyze_string(body)]) + self.assertLen(generator.translation_units, 1) + + generator._get_related_types() + tu = generator.translation_units[0] + tu._process() + + self.assertLen(tu.required_defines, 4) + defines = generator._get_defines() + self.assertLen(defines, 4) + self.assertIn('#define SIZE 1024', defines) + self.assertIn('#define SIZE2 2 * 1024', defines) + self.assertIn('#define SIZE3 1337', defines) + self.assertIn('#define SIZE4 10', defines) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testDoubleFunction(self): + body = """ + extern "C" int function_1(int a); + extern "C" int function_1(int a) { + return a + 1; + }; + """ + generator = code.Generator([analyze_string(body)]) + self.assertLen(generator.translation_units, 1) + + tu = generator.translation_units[0] + tu._process() + + self.assertLen(tu.functions, 1) + + # Extra check for generation, in case rendering throws error for this test. + generator.generate('Test', [], 'sapi::Tests', None, None) + + def testDefineStructBody(self): + body = """ + #define STRUCT_BODY \ + int a; \ + char b; \ + long c + struct test { + STRUCT_BODY; + }; + extern "C" void function(struct test* a1); + """ + + generator = code.Generator([analyze_string(body)]) + self.assertLen(generator.translation_units, 1) + + # initialize all internal data + generator.generate('Test', [], 'sapi::Tests', None, None) + tu = generator.translation_units[0] + + self.assertLen(tu.functions, 1) + self.assertLen(tu.required_defines, 1) + + def testJpegTurboCase(self): + body = """ + typedef short JCOEF; + #define DCTSIZE2 1024 + typedef JCOEF JBLOCK[DCTSIZE2]; + + extern "C" void function(JBLOCK* a); + """ + generator = code.Generator([analyze_string(body)]) + self.assertLen(generator.translation_units, 1) + + # initialize all internal data + generator.generate('Test', [], 'sapi::Tests', None, None) + + tu = generator.translation_units[0] + self.assertLen(tu.functions, 1) + self.assertLen(generator._get_defines(), 1) + self.assertLen(generator._get_related_types(), 2) + + def testMultipleTypesWhenConst(self): + body = """ + struct Instance { + void* instance = nullptr; + void* state_memory = nullptr; + void* scratch_memory = nullptr; + }; + + extern "C" void function1(Instance* a); + extern "C" void function2(const Instance* a); + """ + generator = code.Generator([analyze_string(body)]) + self.assertLen(generator.translation_units, 1) + + # Initialize all internal data + generator.generate('Test', [], 'sapi::Tests', None, None) + + tu = generator.translation_units[0] + self.assertLen(tu.functions, 2) + self.assertLen(generator._get_related_types(), 1) + + def testReference(self): + body = """ + struct Instance { + int a; + }; + + void Function1(Instance& a, Instance&& a); + """ + generator = code.Generator([analyze_string(body)]) + self.assertLen(generator.translation_units, 1) + + # Initialize all internal data + generator.generate('Test', [], 'sapi::Tests', None, None) + + tu = generator.translation_units[0] + self.assertLen(tu.functions, 1) + + # this will return 0 related types because function will be mangled and + # filtered out by generator + self.assertEmpty(generator._get_related_types()) + self.assertLen(next(iter(tu.functions)).get_related_types(), 1) + + def testCppHeader(self): + path = 'tmp.h' + content = """ + int sum(int a, float b); + + extern "C" int sum(int a, float b); + """ + unsaved_files = [(path, content)] + generator = code.Generator([analyze_strings(path, unsaved_files)]) + # Initialize all internal data + generator.generate('Test', [], 'sapi::Tests', None, None) + + # generator should filter out mangled function + functions = generator._get_functions() + self.assertLen(functions, 1) + + tu = generator.translation_units[0] + functions = tu.get_functions() + self.assertLen(functions, 2) + + mangled_names = [f.mangled_name for f in functions] + self.assertSameElements(mangled_names, ['sum', '_Z3sumif']) + + +if __name__ == '__main__': + absltest.main() diff --git a/sandboxed_api/tools/generator2/code_test_util.py b/sandboxed_api/tools/generator2/code_test_util.py new file mode 100644 index 0000000..a96bc30 --- /dev/null +++ b/sandboxed_api/tools/generator2/code_test_util.py @@ -0,0 +1,209 @@ +# 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 golden outputs for tests.""" + + +CODE_GOLD = """// AUTO-GENERATED by the Sandboxed API generator. +// Edits will be discarded when regenerating this file. + +#include "sandboxed_api/sandbox.h" +#include "sandboxed_api/vars.h" + +namespace sapi { +namespace Tests { + + + + +class TestApi { + public: + explicit TestApi(::sapi::Sandbox* sandbox) : sandbox_(sandbox) {} + ::sapi::Sandbox* GetSandbox() { + return sandbox_; + } + + + // int function_a(int, int) + sapi::StatusOr function_a(int x, int y) { + ::sapi::v::Int ret; + ::sapi::v::Int x_((x)); + ::sapi::v::Int y_((y)); + + SAPI_RETURN_IF_ERROR(sandbox_->Call("function_a", &ret, &x_, &y_)); + return ret.GetValue(); + } + + // int types_1(bool, unsigned char, char, unsigned short, short) + sapi::StatusOr types_1(bool a0, unsigned char a1, char a2, unsigned short a3, short a4) { + ::sapi::v::Int ret; + ::sapi::v::Bool a0_((a0)); + ::sapi::v::UChar a1_((a1)); + ::sapi::v::Char a2_((a2)); + ::sapi::v::UShort a3_((a3)); + ::sapi::v::Short a4_((a4)); + + SAPI_RETURN_IF_ERROR(sandbox_->Call("types_1", &ret, &a0_, &a1_, &a2_, &a3_, &a4_)); + return ret.GetValue(); + } + + // int types_2(int, unsigned int, long, unsigned long) + sapi::StatusOr types_2(int a0, unsigned int a1, long a2, unsigned long a3) { + ::sapi::v::Int ret; + ::sapi::v::Int a0_((a0)); + ::sapi::v::UInt a1_((a1)); + ::sapi::v::Long a2_((a2)); + ::sapi::v::ULong a3_((a3)); + + SAPI_RETURN_IF_ERROR(sandbox_->Call("types_2", &ret, &a0_, &a1_, &a2_, &a3_)); + return ret.GetValue(); + } + + // int types_3(long long, unsigned long long, float, double) + sapi::StatusOr types_3(long long a0, unsigned long long a1, float a2, double a3) { + ::sapi::v::Int ret; + ::sapi::v::LLong a0_((a0)); + ::sapi::v::ULLong a1_((a1)); + ::sapi::v::Reg a2_((a2)); + ::sapi::v::Reg a3_((a3)); + + SAPI_RETURN_IF_ERROR(sandbox_->Call("types_3", &ret, &a0_, &a1_, &a2_, &a3_)); + return ret.GetValue(); + } + + // int types_4(signed char, short, int, long) + sapi::StatusOr types_4(signed char a0, short a1, int a2, long a3) { + ::sapi::v::Int ret; + ::sapi::v::SChar a0_((a0)); + ::sapi::v::Short a1_((a1)); + ::sapi::v::Int a2_((a2)); + ::sapi::v::Long a3_((a3)); + + SAPI_RETURN_IF_ERROR(sandbox_->Call("types_4", &ret, &a0_, &a1_, &a2_, &a3_)); + return ret.GetValue(); + } + + // int types_5(long long, long double) + sapi::StatusOr types_5(long long a0, long double a1) { + ::sapi::v::Int ret; + ::sapi::v::LLong a0_((a0)); + ::sapi::v::Reg a1_((a1)); + + SAPI_RETURN_IF_ERROR(sandbox_->Call("types_5", &ret, &a0_, &a1_)); + return ret.GetValue(); + } + + // void types_6(char *) + sapi::Status types_6(::sapi::v::Ptr* a0) { + ::sapi::v::Void ret; + + SAPI_RETURN_IF_ERROR(sandbox_->Call("types_6", &ret, a0)); + return sapi::OkStatus(); + } + + + private: + ::sapi::Sandbox* sandbox_; +}; + +} // namespace Tests +} // namespace sapi + + +""" + +CODE_GOLD_MAPPED = """// AUTO-GENERATED by the Sandboxed API generator. +// Edits will be discarded when regenerating this file. + +#include "sandboxed_api/sandbox.h" +#include "sandboxed_api/vars.h" + +namespace sapi { +namespace Tests { + + +typedef unsigned int uint; +typedef uint * uintp; + + +class TestApi { + public: + explicit TestApi(::sapi::Sandbox* sandbox) : sandbox_(sandbox) {} + ::sapi::Sandbox* GetSandbox() { + return sandbox_; + } + + + // uint function(uintp) + sapi::StatusOr function(::sapi::v::Ptr* a) { + ::sapi::v::UInt ret; + + SAPI_RETURN_IF_ERROR(sandbox_->Call("function", &ret, a)); + return ret.GetValue(); + } + + + private: + ::sapi::Sandbox* sandbox_; +}; + +} // namespace Tests +} // namespace sapi + + +""" + +CODE_ENUM_GOLD = """// AUTO-GENERATED by the Sandboxed API generator. +// Edits will be discarded when regenerating this file. + +#include "sandboxed_api/sandbox.h" +#include "sandboxed_api/vars.h" + +namespace sapi { +namespace Tests { + + +enum ProcessStatus { +\tOK = 0 , +\tERROR = 1 , +}; + + +class TestApi { + public: + explicit TestApi(::sapi::Sandbox* sandbox) : sandbox_(sandbox) {} + ::sapi::Sandbox* GetSandbox() { + return sandbox_; + } + + + // ProcessStatus ProcessDatapoint(ProcessStatus) + sapi::StatusOr ProcessDatapoint(ProcessStatus status) { + ::sapi::v::IntBase ret; + ::sapi::v::IntBase status_((status)); + + SAPI_RETURN_IF_ERROR(sandbox_->Call("ProcessDatapoint", &ret, &status_)); + return static_cast(ret.GetValue()); + } + + + private: + ::sapi::Sandbox* sandbox_; +}; + +} // namespace Tests +} // namespace sapi + + +""" diff --git a/sandboxed_api/tools/generator2/sapi_generator.py b/sandboxed_api/tools/generator2/sapi_generator.py new file mode 100644 index 0000000..0778283 --- /dev/null +++ b/sandboxed_api/tools/generator2/sapi_generator.py @@ -0,0 +1,73 @@ +# 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. + +"""SAPI interface header generator. + +Parses headers to extract type information from functions and generate a SAPI +interface wrapper. +""" +import sys + + +from absl import app +from absl import flags +from absl import logging +import code + +FLAGS = flags.FLAGS + +flags.DEFINE_string('sapi_name', None, 'library name') +flags.DEFINE_string('sapi_out', '', 'output header file') +flags.DEFINE_string('sapi_ns', '', 'namespace') +flags.DEFINE_string('sapi_isystem', '', 'system includes') +flags.DEFINE_list('sapi_functions', [], 'function list to analyze') +flags.DEFINE_list('sapi_in', None, 'input files to analyze') +flags.DEFINE_string('sapi_embed_dir', None, 'directory with embed includes') +flags.DEFINE_string('sapi_embed_name', None, 'name of the embed object') + + +def extract_includes(path, array): + try: + with open(path, 'r') as f: + for line in f: + array.append('-isystem') + array.append(line.strip()) + except IOError: + pass + return array + + +def main(c_flags): + # remove path to current binary + c_flags.pop(0) + logging.debug(FLAGS.sapi_functions) + extract_includes(FLAGS.sapi_isystem, c_flags) + tus = code.Analyzer.process_files(FLAGS.sapi_in, c_flags) + generator = code.Generator(tus) + result = generator.generate(FLAGS.sapi_name, + FLAGS.sapi_functions, + FLAGS.sapi_ns, + FLAGS.sapi_out, + FLAGS.sapi_embed_dir, + FLAGS.sapi_embed_name) + + if FLAGS.sapi_out: + with open(FLAGS.sapi_out, 'w') as out_file: + out_file.write(result) + else: + sys.stdout.write(result) + +if __name__ == '__main__': + flags.mark_flags_as_required(['sapi_name', 'sapi_in']) + app.run(main) diff --git a/sandboxed_api/tools/generator2/testdata/interface.sapi.h.gold b/sandboxed_api/tools/generator2/testdata/interface.sapi.h.gold new file mode 100755 index 0000000..aec1fe1 --- /dev/null +++ b/sandboxed_api/tools/generator2/testdata/interface.sapi.h.gold @@ -0,0 +1,233 @@ +// AUTO-GENERATED by SandboxedAPI generator. Please do not edit. +// go/sandboxed-api + +#ifndef SECURITY_SANDBOXED_API_TOOLS_GENERATOR_TESTS_INTERFACE_H_ +#define SECURITY_SANDBOXED_API_TOOLS_GENERATOR_TESTS_INTERFACE_H_ + +#include "sandboxed_api/tools/generator/tests_sapi_generator_embed.h" +#include "sandboxed_api/sandbox.h" +#include "sandboxed_api/vars.h" + +namespace sapi { +namespace tests { + +class TestsSandbox : public ::sapi::Sandbox { + public: + TestsSandbox() : ::sapi::Sandbox(tests_sapi_generator_embed_create()) {} +}; + + +typedef unsigned int uint; +typedef uint* uint_p; +typedef uint_p* uint_pp; +typedef void (*function_p)(uint, uint_p, uint_pp); +typedef void (*function_p2)(void (*)(int, char), void*); +struct struct_2 { + uint a; + char b; + uint c; + char d; +} ; +typedef struct { + function_p a; + function_p2 b; + void (*c)(int, long int); + uint d; + uint_pp e; + struct struct_2* f; +} struct_t; +typedef char** char_pp; +typedef long int long_arr[8]; +typedef int function_3(int, int); +typedef union { + int a; + char b; +} union_1; +struct struct_1 { + uint a; + char b; + uint c; + char d; +} ; +struct struct_3 { +// Not defined, no debug info about the type +} ; +struct struct_4 { + char a[4]; + int b; +union { + uint a; + char* b; +} c; +struct { + uint a; + char* b; +} d; + function_p func_1; + const char* const* const_1; + const char** const_2; + char* const* const_3; + int (*coef_bits)[16]; +} ; +struct struct_6_def; +typedef struct struct_6_def struct_6; +typedef struct_6* struct_6p; +typedef void (*function_p3)(struct_6p); +struct struct_6_def { + function_p3 fn; +} ; +struct struct_7_def; +struct struct_7_part_def; +typedef struct struct_7_part_def s7part; +typedef struct struct_7_def* s7p; +struct struct_7_part_def { + int x; + int y; + void (*fn)(); +} ; +struct struct_7_def { + s7part part; + int x; +} ; +enum Enumeration { ONE = 0, TWO = 1, THREE = 2 }; +typedef enum { SIX = 6, TEN = 10 } SixOrTen; +enum class Color { RED = 0, GREEN = 20, BLUE = 21 }; +enum class Direction { LEFT = 108, RIGHT = 114 }; +enum Numbers { UNKNOWN = 0, FIVE = 5, SE7EN = 7 }; +typedef Numbers Nums; + + +class TestsApi { + public: + explicit TestsApi(::sapi::Sandbox* sandbox) : sandbox_(sandbox) {} + ::sapi::Sandbox* GetSandbox() { + return sandbox_; + } + + // bool function_using_simple_types(unsigned char a1, signed char a2, unsigned short a3, short a4, unsigned int a5, int a6, long unsigned int a7, long int a8, long long unsigned int a9, long long int a10) + util::StatusOr function_using_simple_types(unsigned char a1, signed char a2, unsigned short a3, short a4, unsigned int a5, int a6, long unsigned int a7, long int a8, long long unsigned int a9, long long int a10) { + ::sapi::v::IntBase ret; + ::sapi::v::UChar a0_((a1)); + ::sapi::v::SChar a1_((a2)); + ::sapi::v::UShort a2_((a3)); + ::sapi::v::Short a3_((a4)); + ::sapi::v::UInt a4_((a5)); + ::sapi::v::Int a5_((a6)); + ::sapi::v::ULong a6_((a7)); + ::sapi::v::Long a7_((a8)); + ::sapi::v::ULLong a8_((a9)); + ::sapi::v::LLong a9_((a10)); + + RETURN_IF_ERROR(sandbox_->Call("function_using_simple_types", &ret, &a0_, &a1_, &a2_, &a3_, &a4_, &a5_, &a6_, &a7_, &a8_, &a9_)); + return ret.GetValue(); + } + // bool function_using_simple_types_continued(float a1, double a2, long double a3) + util::StatusOr function_using_simple_types_continued(float a1, double a2, long double a3) { + ::sapi::v::IntBase ret; + ::sapi::v::Reg a0_((a1)); + ::sapi::v::Reg a1_((a2)); + ::sapi::v::Reg a2_((a3)); + + RETURN_IF_ERROR(sandbox_->Call("function_using_simple_types_continued", &ret, &a0_, &a1_, &a2_)); + return ret.GetValue(); + } + // int function_using_class(const ExampleClass* ptr_to_class) + util::StatusOr function_using_class(::sapi::v::Ptr* ptr_to_class) { + ::sapi::v::Int ret; + + RETURN_IF_ERROR(sandbox_->Call("function_using_class", &ret, ptr_to_class)); + return ret.GetValue(); + } + // int GetSum(const ExampleClass* None) + util::StatusOr GetSum(::sapi::v::Ptr* None) { + ::sapi::v::Int ret; + + RETURN_IF_ERROR(sandbox_->Call("GetSum", &ret)); + return ret.GetValue(); + } + // uint function_using_typedefs(uint_p a1, uint_pp a2, function_p a3, function_p2* a4, struct_t* a5, char_pp a6, long_arr* a7, function_3* a8) + util::StatusOr function_using_typedefs(::sapi::v::Ptr* a1, ::sapi::v::Ptr* a2, ::sapi::v::Ptr* a3, ::sapi::v::Ptr* a4, ::sapi::v::Ptr* a5, ::sapi::v::Ptr* a6, ::sapi::v::Ptr* a7, ::sapi::v::Ptr* a8) { + ::sapi::v::UInt ret; + + RETURN_IF_ERROR(sandbox_->Call("function_using_typedefs", &ret, a1, a2, a3, a4, a5, a6, a7, a8)); + return ret.GetValue(); + } + // int function_using_union(union_1* a1) + util::StatusOr function_using_union(::sapi::v::Ptr* a1) { + ::sapi::v::Int ret; + + RETURN_IF_ERROR(sandbox_->Call("function_using_union", &ret, a1)); + return ret.GetValue(); + } + // unsigned char* function_using_pointers(int* a1, unsigned char* a2, unsigned char a3, const char* a4) + util::StatusOr function_using_pointers(::sapi::v::Ptr* a1, ::sapi::v::Ptr* a2, unsigned char a3, ::sapi::v::Ptr* a4) { + ::sapi::v::Reg ret; + ::sapi::v::UChar a2_((a3)); + + RETURN_IF_ERROR(sandbox_->Call("function_using_pointers", &ret, a1, a2, &a2_, a4)); + return ret.GetValue(); + } + // uint* function_returning_pointer() + util::StatusOr function_returning_pointer() { + ::sapi::v::Reg ret; + + RETURN_IF_ERROR(sandbox_->Call("function_returning_pointer", &ret)); + return ret.GetValue(); + } + // void function_returning_void(int* a) + util::Status function_returning_void(::sapi::v::Ptr* a) { + ::sapi::v::Void ret; + + RETURN_IF_ERROR(sandbox_->Call("function_returning_void", &ret, a)); + return util::OkStatus(); + } + // int function_using_structures(struct struct_1* a1, struct struct_2* a2, struct struct_3* a3, struct struct_4* a4) + util::StatusOr function_using_structures(::sapi::v::Ptr* a1, ::sapi::v::Ptr* a2, ::sapi::v::Ptr* a3, ::sapi::v::Ptr* a4) { + ::sapi::v::Int ret; + + RETURN_IF_ERROR(sandbox_->Call("function_using_structures", &ret, a1, a2, a3, a4)); + return ret.GetValue(); + } + // void function_using_type_loop(struct_6p a1) + util::Status function_using_type_loop(::sapi::v::Ptr* a1) { + ::sapi::v::Void ret; + + RETURN_IF_ERROR(sandbox_->Call("function_using_type_loop", &ret, a1)); + return util::OkStatus(); + } + // void function_using_incomplete(s7p a1) + util::Status function_using_incomplete(::sapi::v::Ptr* a1) { + ::sapi::v::Void ret; + + RETURN_IF_ERROR(sandbox_->Call("function_using_incomplete", &ret, a1)); + return util::OkStatus(); + } + // int function_using_enums(Enumeration a1, SixOrTen a2, Color a3, Direction a4, Nums a5) + util::StatusOr function_using_enums(Enumeration a1, SixOrTen a2, Color a3, Direction a4, Nums a5) { + ::sapi::v::Int ret; + ::sapi::v::Reg a0_((a1)); + ::sapi::v::Reg a1_((a2)); + ::sapi::v::Reg a2_((a3)); + ::sapi::v::Reg a3_((a4)); + ::sapi::v::Reg a4_((a5)); + + RETURN_IF_ERROR(sandbox_->Call("function_using_enums", &ret, &a0_, &a1_, &a2_, &a3_, &a4_)); + return ret.GetValue(); + } + // struct struct_2* function_returning_struct_ptr() + util::StatusOr function_returning_struct_ptr() { + ::sapi::v::Reg ret; + + RETURN_IF_ERROR(sandbox_->Call("function_returning_struct_ptr", &ret)); + return ret.GetValue(); + } + + private: + ::sapi::Sandbox* sandbox_; +}; + +} // namespace tests +} // namespace sapi + + +#endif // SECURITY_SANDBOXED_API_TOOLS_GENERATOR_TESTS_INTERFACE_H_ diff --git a/sandboxed_api/tools/generator2/testdata/main.cc b/sandboxed_api/tools/generator2/testdata/main.cc new file mode 100644 index 0000000..6439edb --- /dev/null +++ b/sandboxed_api/tools/generator2/testdata/main.cc @@ -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. + +#include "sandboxed_api/tools/generator2/tests_sapi_generator.sapi.h" + +int main() { + sapi::tests::struct_t a; + a.d = 1337; + + return a.d - 1337; +} diff --git a/sandboxed_api/tools/generator2/testdata/tests.cc b/sandboxed_api/tools/generator2/testdata/tests.cc new file mode 100644 index 0000000..73aa193 --- /dev/null +++ b/sandboxed_api/tools/generator2/testdata/tests.cc @@ -0,0 +1,200 @@ +// 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. + +// Test file trying to cover as much of DWARF entry combinations as possible. +// Base for testing. +// As we are tracking types related to function calls, types of interest should +// be passed as arguments, returned by function or be part of structure +// dependency chain + +#include "sandboxed_api/tools/generator2/testdata/tests.h" + +namespace a { +namespace b { + +class ExampleClass { + private: + int a_; + int b_; + + public: + int GetSum() const { return a_ + b_; } +}; + +} // namespace b +} // namespace a + +extern "C" { + +// Simple types +bool function_using_simple_types(unsigned char a1, // NOLINT + signed char a2, // NOLINT + unsigned short a3, // NOLINT + signed short a4, // NOLINT + unsigned int a5, // NOLINT + signed int a6, // NOLINT + unsigned long a7, // NOLINT + signed long a8, // NOLINT + unsigned long long a9, // NOLINT + signed long long a10 // NOLINT +) { + return a1 ? true : false; +} + +bool function_using_simple_types_continued(float a1, double a2, + long double a3) { + return a1 ? true : false; +} + +// Class usage +int function_using_class(const a::b::ExampleClass* ptr_to_class) { + return ptr_to_class->GetSum(); +} + +// Typedef usage +typedef unsigned int uint; +typedef uint* uint_p; +typedef uint_p* uint_pp; +typedef char** char_pp; +typedef long int long_arr[8]; // NOLINT +typedef void (*function_p)(uint, uint_p, uint_pp); +typedef void (*function_p2)(void (*)(int, char), void*); +typedef int function_3(int a, int b); + +typedef union { + int a; + char b; +} union_1; + +typedef struct { + function_p a; + function_p2 b; + void (*c)(int, long); // NOLINT + uint d; + uint_pp e; + struct struct_2* f; +} struct_t; + +// Using defined types so these end up in debug symbols +uint function_using_typedefs(uint_p a1, uint_pp a2, function_p a3, + function_p2* a4, struct_t* a5, char_pp a6, + long_arr* a7, function_3* a8) { + return 1337 + a5->d + a8(1, 3); +} + +int function_using_union(union_1* a1) { return a1->a; } + +// Pointer usage +unsigned char* function_using_pointers(int* a1, unsigned char* a2, + unsigned char a3, const char* a4) { + return a2; +} + +uint* function_returning_pointer() { return reinterpret_cast(0x1337); } + +void function_returning_void(int* a) { *a = 1337; } + +// Structures +struct __attribute__((__packed__)) struct_1 { + uint a; + char b; + uint c; + char d; +}; + +struct struct_2 { + uint a; + char b; + uint c; + char d; +}; + +struct struct_3 { + uint partially_defined_struct_so_field_is_invisible; +}; + +#define COEF_BITS_SIZE 16 +struct struct_4 { + char a[4]; + int b; + union { + uint a; + char* b; + } c; + struct { + uint a; + char* b; + } d; + function_p func_1; + // tests for const + ptr issues + const char* const* const_1; + const char** const_2; + char* const* const_3; + int (*coef_bits)[COEF_BITS_SIZE]; +}; + +int function_using_structures(struct struct_1* a1, struct struct_2* a2, + struct struct_3* a3, struct struct_4* a4) { + return a1->a + a2->a + a4->b; +} + +// Tests type loop case typedef -> struct -> fn_ptr -> typedef +struct struct_6_def; +typedef struct struct_6_def struct_6; +typedef struct_6* struct_6p; +typedef void (*function_p3)(struct_6p); +struct struct_6_def { + function_p3 fn; +}; + +void function_using_type_loop(struct_6p a1) { a1->fn(a1); } + +// Tests struct-in-struct case that fails if we generate forward declarations +// for every structure +struct struct_7_part_def { + int x; + int y; + void (*fn)(void); +}; +typedef struct struct_7_part_def s7part; + +struct struct_7_def { + s7part part; + int x; +}; + +typedef struct struct_7_def* s7p; + +void function_using_incomplete(s7p a1) { a1->part.fn(); } + +// Tests for enums +enum Enumeration { ONE, TWO, THREE }; +typedef enum Numbers { UNKNOWN, FIVE = 5, SE7EN = 7 } Nums; +typedef enum { SIX = 6, TEN = 10 } SixOrTen; +enum class Color : long long { RED, GREEN = 20, BLUE }; // NOLINT +enum struct Direction { LEFT = 'l', RIGHT = 'r' }; + +int function_using_enums(Enumeration a1, SixOrTen a2, Color a3, Direction a4, + Nums a5) { + switch (a1) { + case Enumeration::ONE: + return Numbers::SE7EN; + case Enumeration::TWO: + return a2; + default: + return FIVE; + } +} + +} // extern "C" diff --git a/sandboxed_api/tools/generator2/testdata/tests.h b/sandboxed_api/tools/generator2/testdata/tests.h new file mode 100644 index 0000000..fc92391 --- /dev/null +++ b/sandboxed_api/tools/generator2/testdata/tests.h @@ -0,0 +1,18 @@ +// 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_TOOLS_GENERATOR2_TESTDATA_TESTS_H_ +#define SANDBOXED_API_TOOLS_GENERATOR2_TESTDATA_TESTS_H_ + +#endif // SANDBOXED_API_TOOLS_GENERATOR2_TESTDATA_TESTS_H_ diff --git a/sandboxed_api/tools/generator2/testdata/tests2.cc b/sandboxed_api/tools/generator2/testdata/tests2.cc new file mode 100644 index 0000000..829f549 --- /dev/null +++ b/sandboxed_api/tools/generator2/testdata/tests2.cc @@ -0,0 +1,20 @@ +// 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/tools/generator2/testdata/tests.h" + +// This compilation unit should have this structure partially defined +struct struct_2* function_returning_struct_ptr() { + return static_cast(nullptr); +} diff --git a/sandboxed_api/transaction.cc b/sandboxed_api/transaction.cc new file mode 100644 index 0000000..2332bd3 --- /dev/null +++ b/sandboxed_api/transaction.cc @@ -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. + +#include "sandboxed_api/transaction.h" +#include "sandboxed_api/util/canonical_errors.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sapi { + +constexpr absl::Duration TransactionBase::kDefaultTimeLimit; + +sapi::Status TransactionBase::RunTransactionFunctionInSandbox( + const std::function& f) { + // Run Main(), invoking Init() if this hasn't been yet done. + SAPI_RETURN_IF_ERROR(GetSandbox()->Init()); + + // Set the wall-time limit for this transaction run, and clean it up + // afterwards, no matter what the result. + SAPI_RETURN_IF_ERROR(GetSandbox()->SetWallTimeLimit(GetTimeLimit())); + struct TimeCleanup { + ~TimeCleanup() { + if (capture->GetSandbox()->IsActive()) { + capture->GetSandbox()->SetWallTimeLimit(0).IgnoreError(); + } + } + TransactionBase* capture; + } sandbox_cleanup{this}; + + if (!GetInited()) { + SAPI_RETURN_IF_ERROR(Init()); + SetInited(true); + } + + return f(); +} + +sapi::Status TransactionBase::RunTransactionLoop( + const std::function& f) { + // Try to run Main() for a few times, return error if none of the tries + // succeeded. + sapi::Status status; + for (int i = 0; i <= GetRetryCnt(); i++) { + status = RunTransactionFunctionInSandbox(f); + if (status.ok()) { + return status; + } + GetSandbox()->Terminate(); + SetInited(false); + } + + LOG(ERROR) << "Tried " << (GetRetryCnt() + 1) << " times to run the " + << "transaction, but it failed. SAPI error: '" << status + << "'. Latest sandbox error: '" + << GetSandbox()->AwaitResult().ToString() << "'"; + return status; +} + +TransactionBase::~TransactionBase() { + if (GetInited()) { + Finish().IgnoreError(); + } +} + +} // namespace sapi diff --git a/sandboxed_api/transaction.h b/sandboxed_api/transaction.h new file mode 100644 index 0000000..e3199e9 --- /dev/null +++ b/sandboxed_api/transaction.h @@ -0,0 +1,198 @@ +// 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_TRANSACTION_H_ +#define SANDBOXED_API_TRANSACTION_H_ + +#include + +#include +#include "absl/strings/str_cat.h" +#include "absl/time/time.h" +#include "sandboxed_api/sandbox.h" +#include "sandboxed_api/util/canonical_errors.h" +#include "sandboxed_api/util/status.h" +#include "sandboxed_api/util/status_macros.h" + +#define TRANSACTION_FAIL_IF_NOT(x, y) \ + if (!(x)) { \ + return sapi::FailedPreconditionError(y); \ + } + +namespace sapi { + +// The Transaction class allows to perform operations in the sandboxee, +// repeating them if necessary (if the sandboxing, or IPC failed). +// +// We provide two different implementations of transactions: +// 1) Single function transactions - They consist out of a single function +// Main() that will be invoked as body of the transaction. For this, +// inherit from the Transaction class and implement Main(). +// 2) Function pointer based transactions - The BasicTransaction class accepts +// functions that take a sandbox object (along with arbitrary other +// parameters) and return a status. This way no custom implementation of a +// Transaction class is required. +// +// Additionally both methods support Init() and Finish() functions. +// Init() will be called after the sandbox has been set up. +// Finish() will be called when the transaction object goes out of scope. +class TransactionBase { + public: + TransactionBase(const TransactionBase&) = delete; + TransactionBase& operator=(const TransactionBase&) = delete; + virtual ~TransactionBase(); + + // Getter/Setter for retry_cnt_. + int GetRetryCnt() const { return retry_cnt_; } + void SetRetryCnt(int retry_count) { + CHECK_GE(retry_count, 0); + retry_cnt_ = retry_count; + } + + // Getter/Setter for time_limit_. + time_t GetTimeLimit() const { return time_limit_; } + void SetTimeLimit(time_t time_limit) { time_limit_ = time_limit; } + void SetTimeLimit(absl::Duration time_limit) { + time_limit_ = absl::ToTimeT(absl::UnixEpoch() + time_limit); + } + + // Getter/Setter for inited_. + bool GetInited() const { return inited_; } + void SetInited(bool inited) { inited_ = inited; } + + // Getter for the sandbox_. + Sandbox* GetSandbox() { return sandbox_.get(); } + + // Restarts the sandbox. + // WARNING: This will invalidate any references to the remote process, make + // sure you don't keep any var's or FD's to the remote process when calling + // this. + sapi::Status Restart() { + if (inited_) { + Finish().IgnoreError(); + inited_ = false; + } + return sandbox_->Restart(true); + } + + protected: + explicit TransactionBase(std::unique_ptr sandbox) + : retry_cnt_(kDefaultRetryCnt), + time_limit_(absl::ToTimeT(absl::UnixEpoch() + kDefaultTimeLimit)), + inited_(false), + sandbox_(std::move(sandbox)) {} + + // Runs the main (retrying) transaction loop. + sapi::Status RunTransactionLoop(const std::function& f); + + private: + // Number of default transaction execution re-tries, in case of failures. + static constexpr int kDefaultRetryCnt = 1; + + // Wall-time limit for a single transaction execution (60 s.). + static constexpr absl::Duration kDefaultTimeLimit = absl::Seconds(60); + + // Executes a single function in the sandbox, used in the main transaction + // loop. Asserts that the sandbox has been set up and Init() was called. + sapi::Status RunTransactionFunctionInSandbox( + const std::function& f); + + // Initialization routine of the sandboxed process that ill be called only + // once upon sandboxee startup. + virtual sapi::Status Init() { return sapi::OkStatus(); } + + // End routine for the sandboxee that gets calls when the transaction is + // destroyed/restarted to clean up resources. + virtual sapi::Status Finish() { return sapi::OkStatus(); } + + // Number of tries this transaction will be re-executed until it succeeds. + int retry_cnt_; + + // Time (wall-time) limit for a single Run() call (in seconds). 0 means: no + // wall-time limit. + time_t time_limit_; + + // Has Init() finished with success? + bool inited_; + + // The main sapi::Sandbox object. + std::unique_ptr sandbox_; +}; + +// Regular style transactions, based on inheriting. +class Transaction : public TransactionBase { + public: + Transaction(const Transaction&) = delete; + Transaction& operator=(const Transaction&) = delete; + using TransactionBase::TransactionBase; + + // Run the transaction. + sapi::Status Run() { + return RunTransactionLoop([this] { return Main(); }); + } + + protected: + // The main sandboxee routine: Can be called multiple times. + virtual sapi::Status Main() { return sapi::OkStatus(); } +}; + +// Callback style transactions: +class BasicTransaction final : public TransactionBase { + using InitFunction = std::function; + using FinishFunction = std::function; + + public: + explicit BasicTransaction(std::unique_ptr sandbox) + : TransactionBase(std::move(sandbox)), + init_function_(nullptr), + finish_function_(nullptr) {} + + template + BasicTransaction(std::unique_ptr sandbox, F init_function) + : TransactionBase(std::move(sandbox)), + init_function_(static_cast(init_function)), + finish_function_(nullptr) {} + + template + BasicTransaction(std::unique_ptr sandbox, F init_function, + G fini_function) + : TransactionBase(std::move(sandbox)), + init_function_(static_cast(init_function)), + finish_function_(static_cast(fini_function)) {} + + // Run any function as body of the transaction that matches our expectations ( + // that is: Returning a sapi::Status and accepting a Sandbox object as first + // parameter). + template + sapi::Status Run(T func, Args&&... args) { + return RunTransactionLoop( + [&] { return func(GetSandbox(), std::forward(args)...); }); + } + + private: + InitFunction init_function_; + FinishFunction finish_function_; + + sapi::Status Init() final { + return init_function_ ? init_function_(GetSandbox()) : sapi::OkStatus(); + } + + sapi::Status Finish() final { + return finish_function_ ? finish_function_(GetSandbox()) : sapi::OkStatus(); + } +}; + +} // namespace sapi + +#endif // SANDBOXED_API_TRANSACTION_H_ diff --git a/sandboxed_api/util/BUILD.bazel b/sandboxed_api/util/BUILD.bazel new file mode 100644 index 0000000..babb2b8 --- /dev/null +++ b/sandboxed_api/util/BUILD.bazel @@ -0,0 +1,128 @@ +# 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. + +package(default_visibility = ["//sandboxed_api:__subpackages__"]) + +licenses(["notice"]) # Apache 2.0 + +load("//sandboxed_api/bazel:proto.bzl", "sapi_proto_library") + +sapi_proto_library( + name = "status_proto", + srcs = ["status.proto"], +) + +# A custom fork of util/task/status.h. This will become obsolete and will be +# replaced once Abseil releases absl::Status. +cc_library( + name = "status", + srcs = [ + "canonical_errors.cc", + "status.cc", + ], + hdrs = [ + "canonical_errors.h", + "status.h", + "status_internal.h", + "status_macros.h", + ], + deps = [ + ":status_proto_cc", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "statusor", + hdrs = ["statusor.h"], + deps = [ + ":raw_logging", + ":status", + "@com_google_absl//absl/base", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/types:variant", + ], +) + +# gMock matchers for sapi::Status and sapi::StatusOr and a gUnit printer +# extension for sapi::StatusOr. +cc_library( + name = "status_matchers", + testonly = 1, + hdrs = ["status_matchers.h"], + deps = [ + ":status", + ":statusor", + "@com_google_absl//absl/types:optional", + "@com_google_googletest//:gtest", + ], +) + +# Tests for the Status utility. +cc_test( + name = "status_test", + srcs = ["status_test.cc"], + deps = [ + ":status_matchers", + "@com_google_googletest//:gtest_main", + ], +) + +# Tests for the StatusOr template class. +cc_test( + name = "statusor_test", + srcs = ["statusor_test.cc"], + deps = [ + ":status", + ":status_matchers", + ":statusor", + "@com_google_googletest//:gtest_main", + ], +) + +# Tests for the Status macros. +cc_test( + name = "status_macros_test", + srcs = ["status_macros_test.cc"], + deps = [ + ":status", + ":status_matchers", + ":statusor", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +# Compatibility layer for Abseil's flags vs. gFlags +cc_library( + name = "flag", + hdrs = ["flag.h"], + deps = ["@com_github_gflags_gflags//:gflags"], +) + +# Small support library emulating verbose logging using Abseil's raw logging +# facility. +cc_library( + name = "raw_logging", + srcs = ["raw_logging.cc"], + hdrs = ["raw_logging.h"], + deps = [ + "//sandboxed_api/sandbox2/util:strerror", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) diff --git a/sandboxed_api/util/canonical_errors.cc b/sandboxed_api/util/canonical_errors.cc new file mode 100644 index 0000000..65ab738 --- /dev/null +++ b/sandboxed_api/util/canonical_errors.cc @@ -0,0 +1,117 @@ +// 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/util/canonical_errors.h" + +namespace sapi { + +Status AbortedError(absl::string_view message) { + return Status{StatusCode::kAborted, message}; +} +Status AlreadyExistsError(absl::string_view message) { + return Status{StatusCode::kAlreadyExists, message}; +} +Status CancelledError(absl::string_view message) { + return Status{StatusCode::kCancelled, message}; +} +Status DataLossError(absl::string_view message) { + return Status{StatusCode::kDataLoss, message}; +} +Status DeadlineExceededError(absl::string_view message) { + return Status{StatusCode::kDeadlineExceeded, message}; +} +Status FailedPreconditionError(absl::string_view message) { + return Status{StatusCode::kFailedPrecondition, message}; +} +Status InternalError(absl::string_view message) { + return Status{StatusCode::kInternal, message}; +} +Status InvalidArgumentError(absl::string_view message) { + return Status{StatusCode::kInvalidArgument, message}; +} +Status NotFoundError(absl::string_view message) { + return Status{StatusCode::kNotFound, message}; +} +Status OutOfRangeError(absl::string_view message) { + return Status{StatusCode::kOutOfRange, message}; +} +Status PermissionDeniedError(absl::string_view message) { + return Status{StatusCode::kPermissionDenied, message}; +} +Status ResourceExhaustedError(absl::string_view message) { + return Status{StatusCode::kResourceExhausted, message}; +} +Status UnauthenticatedError(absl::string_view message) { + return Status{StatusCode::kUnauthenticated, message}; +} +Status UnavailableError(absl::string_view message) { + return Status{StatusCode::kUnavailable, message}; +} +Status UnimplementedError(absl::string_view message) { + return Status{StatusCode::kUnimplemented, message}; +} +Status UnknownError(absl::string_view message) { + return Status{StatusCode::kUnknown, message}; +} + +bool IsAborted(const Status& status) { + return status.code() == StatusCode::kAborted; +} +bool IsAlreadyExists(const Status& status) { + return status.code() == StatusCode::kAlreadyExists; +} +bool IsCancelled(const Status& status) { + return status.code() == StatusCode::kCancelled; +} +bool IsDataLoss(const Status& status) { + return status.code() == StatusCode::kDataLoss; +} +bool IsDeadlineExceeded(const Status& status) { + return status.code() == StatusCode::kDeadlineExceeded; +} +bool IsFailedPrecondition(const Status& status) { + return status.code() == StatusCode::kFailedPrecondition; +} +bool IsInternal(const Status& status) { + return status.code() == StatusCode::kInternal; +} +bool IsInvalidArgument(const Status& status) { + return status.code() == StatusCode::kInvalidArgument; +} +bool IsNotFound(const Status& status) { + return status.code() == StatusCode::kNotFound; +} +bool IsOutOfRange(const Status& status) { + return status.code() == StatusCode::kOutOfRange; +} +bool IsPermissionDenied(const Status& status) { + return status.code() == StatusCode::kPermissionDenied; +} +bool IsResourceExhausted(const Status& status) { + return status.code() == StatusCode::kResourceExhausted; +} +bool IsUnauthenticated(const Status& status) { + return status.code() == StatusCode::kUnauthenticated; +} +bool IsUnavailable(const Status& status) { + return status.code() == StatusCode::kUnavailable; +} +bool IsUnimplemented(const Status& status) { + return status.code() == StatusCode::kUnimplemented; +} +bool IsUnknown(const Status& status) { + return status.code() == StatusCode::kUnknown; +} + +} // namespace sapi diff --git a/sandboxed_api/util/canonical_errors.h b/sandboxed_api/util/canonical_errors.h new file mode 100644 index 0000000..2d4b623 --- /dev/null +++ b/sandboxed_api/util/canonical_errors.h @@ -0,0 +1,67 @@ +// 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_UTIL_CANONICAL_ERRORS_H_ +#define SANDBOXED_API_UTIL_CANONICAL_ERRORS_H_ + +#include "absl/base/attributes.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/util/status.h" + +namespace sapi { + +// Each of the functions below creates a canonical error with the given +// message. The error code of the returned status object matches the name of +// the function. +Status AbortedError(absl::string_view message); +Status AlreadyExistsError(absl::string_view message); +Status CancelledError(absl::string_view message); +Status DataLossError(absl::string_view message); +Status DeadlineExceededError(absl::string_view message); +Status FailedPreconditionError(absl::string_view message); +Status InternalError(absl::string_view message); +Status InvalidArgumentError(absl::string_view message); +Status NotFoundError(absl::string_view message); +Status OutOfRangeError(absl::string_view message); +Status PermissionDeniedError(absl::string_view message); +Status ResourceExhaustedError(absl::string_view message); +Status UnauthenticatedError(absl::string_view message); +Status UnavailableError(absl::string_view message); +Status UnimplementedError(absl::string_view message); +Status UnknownError(absl::string_view message); + +// Each of the functions below returns true if the given status matches the +// canonical error code implied by the function's name. If necessary, the +// status will be converted to the canonical error space to perform the +// comparison. +ABSL_MUST_USE_RESULT bool IsAborted(const Status& status); +ABSL_MUST_USE_RESULT bool IsAlreadyExists(const Status& status); +ABSL_MUST_USE_RESULT bool IsCancelled(const Status& status); +ABSL_MUST_USE_RESULT bool IsDataLoss(const Status& status); +ABSL_MUST_USE_RESULT bool IsDeadlineExceeded(const Status& status); +ABSL_MUST_USE_RESULT bool IsFailedPrecondition(const Status& status); +ABSL_MUST_USE_RESULT bool IsInternal(const Status& status); +ABSL_MUST_USE_RESULT bool IsInvalidArgument(const Status& status); +ABSL_MUST_USE_RESULT bool IsNotFound(const Status& status); +ABSL_MUST_USE_RESULT bool IsOutOfRange(const Status& status); +ABSL_MUST_USE_RESULT bool IsPermissionDenied(const Status& status); +ABSL_MUST_USE_RESULT bool IsResourceExhausted(const Status& status); +ABSL_MUST_USE_RESULT bool IsUnauthenticated(const Status& status); +ABSL_MUST_USE_RESULT bool IsUnavailable(const Status& status); +ABSL_MUST_USE_RESULT bool IsUnimplemented(const Status& status); +ABSL_MUST_USE_RESULT bool IsUnknown(const Status& status); + +} // namespace sapi + +#endif // SANDBOXED_API_UTIL_CANONICAL_ERRORS_H_ diff --git a/sandboxed_api/util/flag.h b/sandboxed_api/util/flag.h new file mode 100644 index 0000000..12bfccc --- /dev/null +++ b/sandboxed_api/util/flag.h @@ -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. + +#ifndef SANDBOXED_API_UTIL_FLAG_H_ +#define SANDBOXED_API_UTIL_FLAG_H_ +#include + +#define ABSL_FLAG(type, name, default_value, help) \ + DEFINE_##type(name, default_value, help) +#define ABSL_RETIRED_FLAG ABSL_FLAG +#define ABSL_DECLARE_FLAG(type, name) DECLARE_##type(name) + +// Internal defines for compatility with gflags and standard integer types. +#define DECLARE_int32_t DECLARE_int32 +#define DECLARE_int64_t DECLARE_int64 +#define DECLARE_uint32_t DECLARE_uint32 +#define DECLARE_uint64_t DECLARE_uint64 +#define DEFINE_int32_t DEFINE_int32 +#define DEFINE_int64_t DEFINE_int64 +#define DEFINE_uint32_t DEFINE_uint32 +#define DEFINE_uint64_t DEFINE_uint64 + +namespace absl { + +template +const T& GetFlag(const T& flag) { + return flag; +} + +template +void SetFlag(T* flag, const T& value) { + *flag = value; +} + +} // namespace absl + +#endif // SANDBOXED_API_UTIL_FLAG_H_ diff --git a/sandboxed_api/util/raw_logging.cc b/sandboxed_api/util/raw_logging.cc new file mode 100644 index 0000000..cbbca08 --- /dev/null +++ b/sandboxed_api/util/raw_logging.cc @@ -0,0 +1,42 @@ +// 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/util/raw_logging.h" + +#include +#include + +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" + +namespace sapi { +namespace internal { + +bool VLogIsOn(int verbose_level) { + static int external_verbose_level = [] { + int external_verbose_level = std::numeric_limits::max(); + char* env_var = getenv("SAPI_VLOG_LEVEL"); + if (!env_var) { + return external_verbose_level; + } + ABSL_RAW_CHECK(absl::SimpleAtoi(env_var, &external_verbose_level) && + external_verbose_level >= 0, + "SAPI_VLOG_LEVEL needs to be an integer >= 0"); + return external_verbose_level; + }(); + return verbose_level >= external_verbose_level; +} + +} // namespace internal +} // namespace sapi diff --git a/sandboxed_api/util/raw_logging.h b/sandboxed_api/util/raw_logging.h new file mode 100644 index 0000000..d9d1997 --- /dev/null +++ b/sandboxed_api/util/raw_logging.h @@ -0,0 +1,66 @@ +// 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_UTIL_RAW_LOGGING_H_ +#define SANDBOXED_API_UTIL_RAW_LOGGING_H_ + +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "sandboxed_api/sandbox2/util/strerror.h" + +namespace sapi { +namespace internal { + +bool VLogIsOn(int verbose_level); + +} // namespace internal +} // namespace sapi + +// Returns whether SAPI verbose logging is enabled, as determined by the +// SAPI_VLOG_LEVEL environment variable. +#define SAPI_VLOG_IS_ON(verbose_level) ::sapi::internal::VLogIsOn(verbose_level) + +// This is similar to LOG(severity) << format..., but intended for low-level +// modules that cannot use regular logging (i.e. in static initializers). It +// also uses printf-style formatting using absl::StrFormat for type-safety. +#define SAPI_RAW_LOG(severity, format, ...) \ + ABSL_INTERNAL_LOG(severity, absl::StrFormat((format), ##__VA_ARGS__)) + +// Like SAPI_RAW_LOG(), but also logs the current value of errno and its +// corresponding error message. +#define SAPI_RAW_PLOG(severity, format, ...) \ + ABSL_INTERNAL_LOG( \ + severity, absl::StrCat(absl::StrFormat((format), ##__VA_ARGS__), ": ", \ + ::sandbox2::StrError(errno), " [", errno, "]")) + +// If verbose logging is enabled, uses SAPI_RAW_LOG() to log. +#define SAPI_RAW_VLOG(verbose_level, format, ...) \ + if (sapi::internal::VLogIsOn(verbose_level)) { \ + SAPI_RAW_LOG(INFO, (format), ##__VA_ARGS__); \ + } + +// Like regular CHECK(), but uses raw logging and printf-style formatting. +#define SAPI_RAW_CHECK(condition, format, ...) \ + ABSL_INTERNAL_CHECK((condition), absl::StrFormat((format), ##__VA_ARGS__)) + +// Like SAPI_RAW_CHECK(), but also logs errno and a message (similar to +// SAPI_RAW_PLOG()). +#define SAPI_RAW_PCHECK(condition, format, ...) \ + ABSL_INTERNAL_CHECK( \ + (condition), \ + absl::StrCat(absl::StrFormat((format), ##__VA_ARGS__), ": ", \ + ::sandbox2::StrError(errno), " [", errno, "]")) + +#endif // SANDBOXED_API_UTIL_RAW_LOGGING_H_ diff --git a/sandboxed_api/util/status.cc b/sandboxed_api/util/status.cc new file mode 100644 index 0000000..ae2e532 --- /dev/null +++ b/sandboxed_api/util/status.cc @@ -0,0 +1,92 @@ +// 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/util/status.h" + +#include "absl/strings/str_cat.h" + +namespace sapi { +namespace internal { + +std::string CodeEnumToString(StatusCode code) { + switch (code) { + case StatusCode::kOk: + return "OK"; + case StatusCode::kCancelled: + return "CANCELLED"; + case StatusCode::kUnknown: + return "UNKNOWN"; + case StatusCode::kInvalidArgument: + return "INVALID_ARGUMENT"; + case StatusCode::kDeadlineExceeded: + return "DEADLINE_EXCEEDED"; + case StatusCode::kNotFound: + return "NOT_FOUND"; + case StatusCode::kAlreadyExists: + return "ALREADY_EXISTS"; + case StatusCode::kPermissionDenied: + return "PERMISSION_DENIED"; + case StatusCode::kUnauthenticated: + return "UNAUTHENTICATED"; + case StatusCode::kResourceExhausted: + return "RESOURCE_EXHAUSTED"; + case StatusCode::kFailedPrecondition: + return "FAILED_PRECONDITION"; + case StatusCode::kAborted: + return "ABORTED"; + case StatusCode::kOutOfRange: + return "OUT_OF_RANGE"; + case StatusCode::kUnimplemented: + return "UNIMPLEMENTED"; + case StatusCode::kInternal: + return "INTERNAL"; + case StatusCode::kUnavailable: + return "UNAVAILABLE"; + case StatusCode::kDataLoss: + return "DATA_LOSS"; + } + return "UNKNOWN"; +} + +} // namespace internal + +Status::Status() : error_code_{static_cast(StatusCode::kOk)} {} + +Status::Status(Status&& other) + : error_code_(other.error_code_), message_(std::move(other.message_)) { + other.Set(StatusCode::kUnknown, ""); +} + +Status& Status::operator=(Status&& other) { + error_code_ = other.error_code_; + message_ = std::move(other.message_); + other.Set(StatusCode::kUnknown, ""); + return *this; +} + +std::string Status::ToString() const { + return ok() ? "OK" + : absl::StrCat("generic::", + internal::CodeEnumToString( + static_cast(error_code_)), + ": ", message_); +} + +Status OkStatus() { return Status{}; } + +std::ostream& operator<<(std::ostream& os, const Status& status) { + return os << status.ToString(); +} + +} // namespace sapi diff --git a/sandboxed_api/util/status.h b/sandboxed_api/util/status.h new file mode 100644 index 0000000..8a2aa21 --- /dev/null +++ b/sandboxed_api/util/status.h @@ -0,0 +1,138 @@ +// 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 and it's implementation provide a custom fork of +// util/task/status.h. This will become obsolete and will be replaced once +// Abseil releases absl::Status. + +#ifndef THIRD_PARTY_SAPI_UTIL_STATUS_H_ +#define THIRD_PARTY_SAPI_UTIL_STATUS_H_ + +#include +#include +#include + +#include "absl/meta/type_traits.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/util/status.pb.h" +#include "sandboxed_api/util/status_internal.h" + +namespace sapi { + +enum class StatusCode { + kOk = 0, + kCancelled = 1, + kUnknown = 2, + kInvalidArgument = 3, + kDeadlineExceeded = 4, + kNotFound = 5, + kAlreadyExists = 6, + kPermissionDenied = 7, + kResourceExhausted = 8, + kFailedPrecondition = 9, + kAborted = 10, + kOutOfRange = 11, + kUnimplemented = 12, + kInternal = 13, + kUnavailable = 14, + kDataLoss = 15, + kUnauthenticated = 16, +}; + +namespace internal { + +std::string CodeEnumToString(StatusCode code); + +} // namespace internal + +class Status { + public: + Status(); + + template + Status(Enum code, absl::string_view message) { + Set(code, message); + } + + Status(const Status&) = default; + Status(Status&& other); + + template ::is_status>> + explicit Status(const StatusT& other) { + Set(status_internal::status_type_traits::CanonicalCode(other), + other.message()); + } + + Status& operator=(const Status&) = default; + Status& operator=(Status&& other); + + template ::is_status>> + StatusT ToOtherStatus() { + return StatusT(status_internal::ErrorCodeHolder(error_code_), message_); + } + + int error_code() const { return error_code_; } + absl::string_view error_message() const { return message_; } + absl::string_view message() const { return message_; } + ABSL_MUST_USE_RESULT bool ok() const { return error_code_ == 0; } + StatusCode code() const { return static_cast(error_code_); } + + std::string ToString() const; + + void IgnoreError() const {} + + private: + template + void Set(Enum code, StringViewT message) { + error_code_ = static_cast(code); + if (error_code_ != 0) { + message_ = std::string(message); + } else { + message_.clear(); + } + } + + int error_code_; + std::string message_; +}; + +Status OkStatus(); + +inline bool operator==(const Status& lhs, const Status& rhs) { + return (lhs.error_code() == rhs.error_code()) && + (lhs.error_message() == rhs.error_message()); +} + +inline bool operator!=(const Status& lhs, const Status& rhs) { + return !(lhs == rhs); +} + +std::ostream& operator<<(std::ostream& os, const Status& status); + +inline void SaveStatusToProto(const Status& status, StatusProto* out) { + out->set_code(status.error_code()); + out->set_error_message(std::string(status.error_message())); +} + +inline Status MakeStatusFromProto(const StatusProto& proto) { + return Status(proto.code(), proto.error_message()); +} + +} // namespace sapi + +#endif // THIRD_PARTY_SAPI_UTIL_STATUS_H_ diff --git a/sandboxed_api/util/status.proto b/sandboxed_api/util/status.proto new file mode 100644 index 0000000..2eecd2e --- /dev/null +++ b/sandboxed_api/util/status.proto @@ -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 = "proto3"; + +package sapi; + +message StatusProto { + reserved 2; // For wire compatibility with the original StatusProto + int32 code = 1; + string error_message = 3; +}; diff --git a/sandboxed_api/util/status_internal.h b/sandboxed_api/util/status_internal.h new file mode 100644 index 0000000..e18a27d --- /dev/null +++ b/sandboxed_api/util/status_internal.h @@ -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 THIRD_PARTY_SAPI_UTIL_STATUS_INTERNAL_H_ +#define THIRD_PARTY_SAPI_UTIL_STATUS_INTERNAL_H_ + +#include +#include + +#include "absl/meta/type_traits.h" + +namespace sapi { +namespace status_internal { + +struct ErrorCodeHolder { + explicit ErrorCodeHolder(int code) : error_code(code) {} + + template ::value>> + operator EnumT() { // NOLINT(runtime/explicit) + return static_cast(error_code); + } + int error_code; +}; + +template +struct status_type_traits { + private: + template + static auto CheckMinimalApi(StatusU* s, int* i, std::string* str, bool* b) + -> decltype(StatusU(ErrorCodeHolder(0), ""), *i = s->error_code(), + *str = s->error_message(), *b = s->ok(), std::true_type()); + + template + static auto CheckMinimalApi(...) -> decltype(std::false_type()); + using minimal_api_type = decltype( + CheckMinimalApi(static_cast(0), static_cast(0), + static_cast(0), static_cast(0))); + + public: + static constexpr bool is_status = minimal_api_type::value; +}; + +} // namespace status_internal +} // namespace sapi + +#endif // THIRD_PARTY_SAPI_UTIL_STATUS_INTERNAL_H_ diff --git a/sandboxed_api/util/status_macros.h b/sandboxed_api/util/status_macros.h new file mode 100644 index 0000000..23719e2 --- /dev/null +++ b/sandboxed_api/util/status_macros.h @@ -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. + +// This file is a custom fork of util/task/status_macros.h. This will become +// obsolete and will be replaced once Abseil releases absl::Status. + +#ifndef THIRD_PARTY_SAPI_UTIL_STATUS_MACROS_H_ +#define THIRD_PARTY_SAPI_UTIL_STATUS_MACROS_H_ + +#include "absl/base/optimization.h" +#include "sandboxed_api/util/status.h" + +// Internal helper for concatenating macro values. +#define SAPI_MACROS_IMPL_CONCAT_INNER_(x, y) x##y +#define SAPI_MACROS_IMPL_CONCAT(x, y) SAPI_MACROS_IMPL_CONCAT_INNER_(x, y) + +#define SAPI_RETURN_IF_ERROR(expr) \ + do { \ + const auto status = (expr); \ + if (ABSL_PREDICT_FALSE(!status.ok())) { \ + return status; \ + } \ + } while (0); + +#define SAPI_ASSIGN_OR_RETURN(lhs, rexpr) \ + SAPI_ASSIGN_OR_RETURN_IMPL( \ + SAPI_MACROS_IMPL_CONCAT(_sapi_statusor, __LINE__), lhs, rexpr) + +#define SAPI_ASSIGN_OR_RETURN_IMPL(statusor, lhs, rexpr) \ + auto statusor = (rexpr); \ + if (ABSL_PREDICT_FALSE(!statusor.ok())) { \ + return statusor.status(); \ + } \ + lhs = std::move(statusor).ValueOrDie(); + +#endif // THIRD_PARTY_SAPI_UTIL_STATUS_MACROS_H_ diff --git a/sandboxed_api/util/status_macros_test.cc b/sandboxed_api/util/status_macros_test.cc new file mode 100644 index 0000000..09b9de4 --- /dev/null +++ b/sandboxed_api/util/status_macros_test.cc @@ -0,0 +1,128 @@ +// 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/util/status_macros.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/util/status.h" +#include "sandboxed_api/util/status_matchers.h" +#include "sandboxed_api/util/statusor.h" + +namespace sapi { +namespace { + +TEST(ReturnIfError, ReturnsOnErrorStatus) { + auto func = []() -> Status { + SAPI_RETURN_IF_ERROR(OkStatus()); + SAPI_RETURN_IF_ERROR(OkStatus()); + SAPI_RETURN_IF_ERROR(Status(sapi::StatusCode::kUnknown, "EXPECTED")); + return Status(sapi::StatusCode::kUnknown, "ERROR"); + }; + + EXPECT_THAT(func(), StatusIs(sapi::StatusCode::kUnknown, "EXPECTED")); +} + +TEST(ReturnIfError, ReturnsOnErrorFromLambda) { + auto func = []() -> Status { + SAPI_RETURN_IF_ERROR([] { return sapi::OkStatus(); }()); + SAPI_RETURN_IF_ERROR( + [] { return Status(sapi::StatusCode::kUnknown, "EXPECTED"); }()); + return Status(sapi::StatusCode::kUnknown, "ERROR"); + }; + + EXPECT_THAT(func(), StatusIs(sapi::StatusCode::kUnknown, "EXPECTED")); +} + +TEST(AssignOrReturn, AssignsMultipleVariablesInSequence) { + auto func = []() -> Status { + int value1; + SAPI_ASSIGN_OR_RETURN(value1, StatusOr(1)); + EXPECT_EQ(1, value1); + int value2; + SAPI_ASSIGN_OR_RETURN(value2, StatusOr(2)); + EXPECT_EQ(2, value2); + int value3; + SAPI_ASSIGN_OR_RETURN(value3, StatusOr(3)); + EXPECT_EQ(3, value3); + int value4; + SAPI_ASSIGN_OR_RETURN( + value4, StatusOr(Status(sapi::StatusCode::kUnknown, "EXPECTED"))); + return Status(sapi::StatusCode::kUnknown, + absl::StrCat("ERROR: assigned value ", value4)); + }; + + EXPECT_THAT(func(), StatusIs(sapi::StatusCode::kUnknown, "EXPECTED")); +} + +TEST(AssignOrReturn, AssignsRepeatedlyToSingleVariable) { + auto func = []() -> Status { + int value = 1; + SAPI_ASSIGN_OR_RETURN(value, StatusOr(2)); + EXPECT_EQ(2, value); + SAPI_ASSIGN_OR_RETURN(value, StatusOr(3)); + EXPECT_EQ(3, value); + SAPI_ASSIGN_OR_RETURN( + value, StatusOr(Status(sapi::StatusCode::kUnknown, "EXPECTED"))); + return Status(sapi::StatusCode::kUnknown, "ERROR"); + }; + + EXPECT_THAT(func(), StatusIs(sapi::StatusCode::kUnknown, "EXPECTED")); +} + +TEST(AssignOrReturn, MovesUniquePtr) { + auto func = []() -> Status { + std::unique_ptr ptr; + SAPI_ASSIGN_OR_RETURN( + ptr, StatusOr>(absl::make_unique(1))); + EXPECT_EQ(*ptr, 1); + return Status(sapi::StatusCode::kUnknown, "EXPECTED"); + }; + + EXPECT_THAT(func(), StatusIs(sapi::StatusCode::kUnknown, "EXPECTED")); +} + +TEST(AssignOrReturn, DoesNotAssignUniquePtrOnErrorStatus) { + auto func = []() -> Status { + std::unique_ptr ptr; + SAPI_ASSIGN_OR_RETURN(ptr, StatusOr>(Status( + sapi::StatusCode::kUnknown, "EXPECTED"))); + EXPECT_EQ(ptr, nullptr); + return OkStatus(); + }; + + EXPECT_THAT(func(), StatusIs(sapi::StatusCode::kUnknown, "EXPECTED")); +} + +TEST(AssignOrReturn, MovesUniquePtrRepeatedlyToSingleVariable) { + auto func = []() -> Status { + std::unique_ptr ptr; + SAPI_ASSIGN_OR_RETURN( + ptr, StatusOr>(absl::make_unique(1))); + EXPECT_EQ(*ptr, 1); + SAPI_ASSIGN_OR_RETURN( + ptr, StatusOr>(absl::make_unique(2))); + EXPECT_EQ(*ptr, 2); + return Status(sapi::StatusCode::kUnknown, "EXPECTED"); + }; + + EXPECT_THAT(func(), StatusIs(sapi::StatusCode::kUnknown, "EXPECTED")); +} + +} // namespace +} // namespace sapi diff --git a/sandboxed_api/util/status_matchers.h b/sandboxed_api/util/status_matchers.h new file mode 100644 index 0000000..d933768 --- /dev/null +++ b/sandboxed_api/util/status_matchers.h @@ -0,0 +1,120 @@ +// 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_UTIL_STATUS_MATCHERS_H_ +#define SANDBOXED_API_UTIL_STATUS_MATCHERS_H_ + +#include "gmock/gmock.h" +#include "absl/types/optional.h" +#include "sandboxed_api/util/status.h" +#include "sandboxed_api/util/status_macros.h" +#include "sandboxed_api/util/statusor.h" + +#define SAPI_ASSERT_OK_AND_ASSIGN(lhs, rexpr) \ + SAPI_ASSERT_OK_AND_ASSIGN_IMPL( \ + SAPI_MACROS_IMPL_CONCAT(_sapi_statusor, __LINE__), lhs, rexpr) + +#define SAPI_ASSERT_OK_AND_ASSIGN_IMPL(statusor, lhs, rexpr) \ + auto statusor = (rexpr); \ + ASSERT_THAT(statusor.status(), sapi::IsOk()); \ + lhs = std::move(statusor).ValueOrDie(); + +namespace sapi { +namespace internal { + +class IsOkMatcher { + public: + template + bool MatchAndExplain(const StatusT& status_container, + ::testing::MatchResultListener* listener) const { + if (!status_container.ok()) { + *listener << "which is not OK"; + return false; + } + return true; + } + + void DescribeTo(std::ostream* os) const { *os << "is OK"; } + + void DescribeNegationTo(std::ostream* os) const { *os << "is not OK"; } +}; + +template +class StatusIsMatcher { + public: + StatusIsMatcher(const StatusIsMatcher&) = default; + StatusIsMatcher& operator=(const StatusIsMatcher&) = default; + + StatusIsMatcher(Enum code, absl::optional message) + : code_{code}, message_{message} {} + + template + bool MatchAndExplain(const StatusT& status, + ::testing::MatchResultListener* listener) const { + if (code_ != status.code()) { + *listener << "whose error code is generic::" + << internal::CodeEnumToString(status.code()); + return false; + } + if (message_.has_value() && status.error_message() != message_.value()) { + *listener << "whose error message is '" << message_.value() << "'"; + return false; + } + return true; + } + + template + bool MatchAndExplain(const StatusOr& status_or, + ::testing::MatchResultListener* listener) const { + return MatchAndExplain(status_or.status(), listener); + } + + void DescribeTo(std::ostream* os) const { + *os << "has a status code that is generic::" + << internal::CodeEnumToString(code_); + if (message_.has_value()) { + *os << ", and has an error message that is '" << message_.value() << "'"; + } + } + + void DescribeNegationTo(std::ostream* os) const { + *os << "has a status code that is not generic::" + << internal::CodeEnumToString(code_); + if (message_.has_value()) { + *os << ", and has an error message that is not '" << message_.value() + << "'"; + } + } + + private: + const Enum code_; + const absl::optional message_; +}; + +} // namespace internal + +inline ::testing::PolymorphicMatcher IsOk() { + return ::testing::MakePolymorphicMatcher(internal::IsOkMatcher{}); +} + +template +::testing::PolymorphicMatcher> StatusIs( + Enum code, absl::optional message = absl::nullopt) { + return ::testing::MakePolymorphicMatcher( + internal::StatusIsMatcher{code, message}); +} + +} // namespace sapi + +#endif // SANDBOXED_API_UTIL_STATUS_MATCHERS_H_ diff --git a/sandboxed_api/util/status_test.cc b/sandboxed_api/util/status_test.cc new file mode 100644 index 0000000..45b3c64 --- /dev/null +++ b/sandboxed_api/util/status_test.cc @@ -0,0 +1,221 @@ +// 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/util/status.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/util/status_matchers.h" + +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::Ne; +using ::testing::Not; +using ::testing::StrEq; + +namespace sapi { +namespace { + +constexpr char kErrorMessage1[] = "Bad foo argument"; +constexpr char kErrorMessage2[] = "Internal foobar error"; + +TEST(StatusTest, OkSuccess) { EXPECT_THAT(OkStatus(), IsOk()); } + +TEST(StatusTest, OkFailure) { + Status status{StatusCode::kInvalidArgument, kErrorMessage1}; + EXPECT_THAT(status, Not(IsOk())); +} + +TEST(StatusTest, GetErrorCodeOkStatus) { + EXPECT_THAT(OkStatus().code(), Eq(StatusCode::kOk)); +} + +TEST(StatusTest, GetErrorCodeNonOkStatus) { + Status status{StatusCode::kInvalidArgument, kErrorMessage1}; + EXPECT_THAT(status.code(), Eq(StatusCode::kInvalidArgument)); +} + +TEST(StatusTest, GetErrorMessageOkStatus) { + EXPECT_THAT(OkStatus().error_message(), IsEmpty()); +} + +TEST(StatusTest, GetErrorMessageNonOkStatus) { + Status status{StatusCode::kInvalidArgument, kErrorMessage1}; + EXPECT_THAT(status.error_message(), Eq(kErrorMessage1)); +} + +TEST(StatusTest, ToStringOkStatus) { + Status status = OkStatus(); + std::string error_code_name = internal::CodeEnumToString(status.code()); + + // The ToString() representation for an ok Status should contain the error + // code name. + std::string status_rep = status.ToString(); + EXPECT_THAT(status_rep.find(error_code_name), Ne(std::string::npos)); +} + +TEST(StatusTest, ToStringNonOkStatus) { + Status status{StatusCode::kInvalidArgument, kErrorMessage1}; + std::string error_code_name = internal::CodeEnumToString(status.code()); + constexpr char kErrorSpaceName[] = "generic"; + // The format of ToString() is subject to change for a non-ok Status, but it + // should contain the error space name, the error code name, and the error + // message. + std::string status_rep = status.ToString(); + EXPECT_THAT(status_rep.find(kErrorSpaceName), Ne(std::string::npos)); + EXPECT_THAT(status_rep.find(error_code_name), Ne(std::string::npos)); + EXPECT_THAT(status_rep.find(std::string(status.error_message())), + Ne(std::string::npos)); +} + +TEST(StatusTest, Equality) { + Status ok_status = OkStatus(); + Status error_status(StatusCode::kInvalidArgument, kErrorMessage1); + + EXPECT_THAT(ok_status == ok_status, IsTrue()); + EXPECT_THAT(error_status == error_status, IsTrue()); + EXPECT_THAT(ok_status == error_status, IsFalse()); +} + +TEST(StatusTest, Inequality) { + Status ok_status = OkStatus(); + Status invalid_arg_status(StatusCode::kInvalidArgument, kErrorMessage1); + Status internal_status(StatusCode::kInternal, kErrorMessage2); + + EXPECT_THAT(ok_status != ok_status, IsFalse()); + EXPECT_THAT(invalid_arg_status != invalid_arg_status, IsFalse()); + + EXPECT_THAT(ok_status != invalid_arg_status, IsTrue()); + EXPECT_THAT(invalid_arg_status != ok_status, IsTrue()); + + EXPECT_THAT(invalid_arg_status != internal_status, IsTrue()); + EXPECT_THAT(internal_status != invalid_arg_status, IsTrue()); +} + +TEST(StatusTest, IsPositiveTest) { + EXPECT_THAT(OkStatus().code(), Eq(StatusCode::kOk)); + + Status invalid_arg_status(StatusCode::kInvalidArgument, kErrorMessage1); + EXPECT_THAT(invalid_arg_status.code(), Eq(StatusCode::kInvalidArgument)); +} + +TEST(StatusTest, IsNegativeTest) { + // Verify correctness of Is() within an error space. + Status invalid_arg_status(StatusCode::kInvalidArgument, kErrorMessage1); + EXPECT_THAT(invalid_arg_status.code(), Ne(StatusCode::kOk)); +} + +TEST(StatusTest, StatusIsMatcher) { + EXPECT_THAT(OkStatus(), StatusIs(StatusCode::kOk)); + + Status invalid_arg_status(StatusCode::kInvalidArgument, kErrorMessage1); + EXPECT_THAT(invalid_arg_status, StatusIs(StatusCode::kInvalidArgument)); +} + +TEST(StatusTest, IsOkMatcher) { + EXPECT_THAT(OkStatus(), IsOk()); + + // Negation of IsOk() matcher. + Status einval_status(StatusCode::kInvalidArgument, kErrorMessage1); + EXPECT_THAT(einval_status, Not(IsOk())); +} + +TEST(StatusTest, MoveConstructorTest) { + Status invalid_arg_status(StatusCode::kInvalidArgument, kErrorMessage1); + EXPECT_THAT(invalid_arg_status, StatusIs(StatusCode::kInvalidArgument)); + + Status that(std::move(invalid_arg_status)); + + EXPECT_THAT(that, StatusIs(StatusCode::kInvalidArgument)); + // NOLINTNEXTLINE + EXPECT_THAT(invalid_arg_status, StatusIs(StatusCode::kUnknown)); +} + +TEST(StatusTest, MoveAssignmentTestNonOk) { + Status invalid_arg_status(StatusCode::kInvalidArgument, kErrorMessage1); + EXPECT_THAT(invalid_arg_status, StatusIs(StatusCode::kInvalidArgument)); + + Status that(StatusCode::kCancelled, kErrorMessage2); + that = std::move(invalid_arg_status); + + EXPECT_THAT(that, StatusIs(StatusCode::kInvalidArgument)); + // NOLINTNEXTLINE + EXPECT_THAT(invalid_arg_status, StatusIs(StatusCode::kUnknown)); +} + +TEST(StatusTest, MoveAssignmentTestOk) { + Status invalid_arg_status(StatusCode::kInvalidArgument, kErrorMessage1); + EXPECT_THAT(invalid_arg_status, StatusIs(StatusCode::kInvalidArgument)); + + Status ok = OkStatus(); + invalid_arg_status = std::move(ok); + + EXPECT_THAT(invalid_arg_status, StatusIs(StatusCode::kOk, "")); + // NOLINTNEXTLINE + EXPECT_THAT(ok, StatusIs(StatusCode::kUnknown)); +} + +TEST(StatusTest, CopyConstructorTestOk) { + Status that(OkStatus()); + + EXPECT_THAT(that, IsOk()); + EXPECT_TRUE(that.error_message().empty()); +} + +TEST(StatusTest, CopyConstructorTestNonOk) { + Status invalid_arg_status(StatusCode::kInvalidArgument, kErrorMessage1); + EXPECT_THAT(invalid_arg_status, StatusIs(StatusCode::kInvalidArgument)); + + Status that(invalid_arg_status); + + EXPECT_THAT(that, StatusIs(StatusCode::kInvalidArgument)); +} + +StatusProto OkStatusProto() { + StatusProto proto; + proto.set_code(static_cast(StatusCode::kOk)); + return proto; +} + +StatusProto InvalidArgumentStatusProto(absl::string_view msg) { + StatusProto proto; + proto.set_code(static_cast(StatusCode::kInvalidArgument)); + proto.set_error_message(std::string(msg)); + return proto; +} + +TEST(StatusTest, SaveStatusToProto) { + { + StatusProto proto; + SaveStatusToProto(OkStatus(), &proto); + const auto ok_proto = OkStatusProto(); + EXPECT_THAT(proto.code(), Eq(ok_proto.code())); + EXPECT_THAT(proto.error_message(), StrEq(ok_proto.error_message())); + } + { + Status status(StatusCode::kInvalidArgument, kErrorMessage1); + StatusProto proto; + SaveStatusToProto(status, &proto); + const auto invalid_proto = InvalidArgumentStatusProto(kErrorMessage1); + EXPECT_THAT(proto.code(), Eq(invalid_proto.code())); + EXPECT_THAT(proto.error_message(), StrEq(invalid_proto.error_message())); + } +} + +} // namespace +} // namespace sapi diff --git a/sandboxed_api/util/statusor.h b/sandboxed_api/util/statusor.h new file mode 100644 index 0000000..efd9124 --- /dev/null +++ b/sandboxed_api/util/statusor.h @@ -0,0 +1,115 @@ +// 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 and it's implementation provide a custom fork of +// util/task/statusor.h. This will become obsolete and will be replaced once +// Abseil releases absl::Status. + +#ifndef THIRD_PARTY_SAPI_UTIL_STATUSOR_H_ +#define THIRD_PARTY_SAPI_UTIL_STATUSOR_H_ + +#include "absl/base/internal/raw_logging.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/types/variant.h" +#include "sandboxed_api/util/raw_logging.h" +#include "sandboxed_api/util/status.h" + +namespace sapi { + +template +class StatusOr { + public: + explicit StatusOr() : variant_{Status{StatusCode::kUnknown, ""}} {} + + StatusOr(const Status& status) : variant_{status} { EnsureNotOk(); } + + StatusOr& operator=(const Status& status) { + variant_ = status; + EnsureNotOk(); + } + + StatusOr(const T& value) : variant_{value} {} + StatusOr(T&& value) : variant_{std::move(value)} {} + + StatusOr(const StatusOr&) = default; + StatusOr& operator=(const StatusOr&) = default; + + StatusOr(StatusOr&&) = default; + StatusOr& operator=(StatusOr&&) = default; + + template + StatusOr(const StatusOr& other) { + *this = other; + } + + template + StatusOr& operator=(const StatusOr& other) { + if (other.ok()) { + variant_ = other.ValueOrDie(); + } else { + variant_ = other.status(); + } + return *this; + } + + explicit operator bool() const { return ok(); } + ABSL_MUST_USE_RESULT bool ok() const { + return absl::holds_alternative(variant_); + } + + Status status() const { + return ok() ? OkStatus() : absl::get(variant_); + } + + const T& ValueOrDie() const& { + EnsureOk(); + return absl::get(variant_); + } + + T& ValueOrDie() & { + EnsureOk(); + return absl::get(variant_); + } + + T ValueOrDie() && { + EnsureOk(); + T tmp(std::move(absl::get(variant_))); + return std::move(tmp); + } + + private: + void EnsureOk() const { + if (!ok()) { + // GoogleTest needs this exact error message for death tests to work. + SAPI_RAW_LOG(FATAL, + "Attempting to fetch value instead of handling error %s", + status().message()); + } + } + + void EnsureNotOk() const { + if (ok()) { + SAPI_RAW_LOG( + FATAL, + "An OK status is not a valid constructor argument to StatusOr"); + } + } + + absl::variant variant_; +}; + +} // namespace sapi + +#endif // THIRD_PARTY_SAPI_UTIL_STATUSOR_H_ diff --git a/sandboxed_api/util/statusor_test.cc b/sandboxed_api/util/statusor_test.cc new file mode 100644 index 0000000..002f7d8 --- /dev/null +++ b/sandboxed_api/util/statusor_test.cc @@ -0,0 +1,370 @@ +// 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 a custom fork of the version in Asylo. This will become obsolete +// and will be replaced once Abseil releases absl::Status. + +#include "sandboxed_api/util/statusor.h" + +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/util/status_matchers.h" + +using ::testing::Eq; +using ::testing::IsFalse; +using ::testing::Not; + +namespace sapi { +namespace { + +constexpr auto kErrorCode = StatusCode::kInvalidArgument; +constexpr char kErrorMessage[] = "Invalid argument"; + +const int kIntElement = 47; +constexpr char kStringElement[] = "47 is 42, corrected for inflation"; + +// A data type without a default constructor. +struct Foo { + int bar; + std::string baz; + + explicit Foo(int value) : bar(value), baz(kStringElement) {} +}; + +// A data type with dynamically-allocated data. +struct HeapAllocatedObject { + int* value; + + HeapAllocatedObject() { + value = new int; + *value = kIntElement; + } + + HeapAllocatedObject(const HeapAllocatedObject& other) { *this = other; } + + HeapAllocatedObject& operator=(const HeapAllocatedObject& other) { + value = new int; + *value = *other.value; + return *this; + } + + HeapAllocatedObject(HeapAllocatedObject&& other) { *this = std::move(other); } + + HeapAllocatedObject& operator=(HeapAllocatedObject&& other) { + value = other.value; + other.value = nullptr; + return *this; + } + + ~HeapAllocatedObject() { delete value; } +}; + +// Constructs a Foo. +struct FooCtor { + using value_type = Foo; + + Foo operator()() { return Foo(kIntElement); } +}; + +// Constructs a HeapAllocatedObject. +struct HeapAllocatedObjectCtor { + using value_type = HeapAllocatedObject; + + HeapAllocatedObject operator()() { return HeapAllocatedObject(); } +}; + +// Constructs an integer. +struct IntCtor { + using value_type = int; + + int operator()() { return kIntElement; } +}; + +// Constructs a string. +struct StringCtor { + using value_type = std::string; + + std::string operator()() { return std::string(kStringElement); } +}; + +// Constructs a vector of strings. +struct StringVectorCtor { + using value_type = std::vector; + + std::vector operator()() { return {kStringElement, kErrorMessage}; } +}; + +bool operator==(const Foo& lhs, const Foo& rhs) { + return (lhs.bar == rhs.bar) && (lhs.baz == rhs.baz); +} + +bool operator==(const HeapAllocatedObject& lhs, + const HeapAllocatedObject& rhs) { + return *lhs.value == *rhs.value; +} + +// Returns an rvalue reference to the StatusOr object pointed to by +// |statusor|. +template +StatusOr&& MoveStatusOr(StatusOr* statusor) { + return std::move(*statusor); +} + +// A test fixture is required for typed tests. +template +class StatusOrTest : public ::testing::Test {}; + +using TestTypes = ::testing::Types; +TYPED_TEST_SUITE(StatusOrTest, TestTypes); + +// Verify that the default constructor for StatusOr constructs an object with a +// non-ok status. +TYPED_TEST(StatusOrTest, ConstructorDefault) { + StatusOr statusor; + EXPECT_THAT(statusor.ok(), IsFalse()); + EXPECT_THAT(statusor.status().code(), Eq(StatusCode::kUnknown)); +} + +// Verify that StatusOr can be constructed from a Status object. +TYPED_TEST(StatusOrTest, ConstructorStatus) { + StatusOr statusor{ + Status{kErrorCode, kErrorMessage}}; + + EXPECT_THAT(statusor.ok(), IsFalse()); + EXPECT_THAT(statusor.status().ok(), IsFalse()); + EXPECT_THAT(statusor.status(), Eq(Status(kErrorCode, kErrorMessage))); +} + +// Verify that StatusOr can be constructed from an object of its element type. +TYPED_TEST(StatusOrTest, ConstructorElementConstReference) { + auto value = TypeParam()(); + StatusOr statusor{value}; + + ASSERT_THAT(statusor, IsOk()); + ASSERT_THAT(statusor.status(), IsOk()); + EXPECT_THAT(statusor.ValueOrDie(), Eq(value)); +} + +// Verify that StatusOr can be constructed from an rvalue reference of an object +// of its element type. +TYPED_TEST(StatusOrTest, ConstructorElementRValue) { + auto value = TypeParam()(); + auto value_copy(value); + StatusOr statusor(std::move(value)); + + ASSERT_THAT(statusor, IsOk()); + ASSERT_THAT(statusor.status(), IsOk()); + + // Compare to a copy of the original value, since the original was moved. + EXPECT_THAT(statusor.ValueOrDie(), Eq(value_copy)); +} + +// Verify that StatusOr can be copy-constructed from a StatusOr with a non-ok +// status. +TYPED_TEST(StatusOrTest, CopyConstructorNonOkStatus) { + StatusOr statusor1 = + Status{kErrorCode, kErrorMessage}; + StatusOr statusor2{statusor1}; + + EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); + EXPECT_THAT(statusor1.status(), Eq(statusor2.status())); +} + +// Verify that StatusOr can be copy-constructed from a StatusOr with an ok +// status. +TYPED_TEST(StatusOrTest, CopyConstructorOkStatus) { + StatusOr statusor1{TypeParam()()}; + StatusOr statusor2{statusor1}; + + EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); + ASSERT_THAT(statusor2, IsOk()); + EXPECT_THAT(statusor1.ValueOrDie(), Eq(statusor2.ValueOrDie())); +} + +// Verify that copy-assignment of a StatusOr with a non-ok is working as +// expected. +TYPED_TEST(StatusOrTest, CopyAssignmentNonOkStatus) { + StatusOr statusor1{ + Status(kErrorCode, kErrorMessage)}; + StatusOr statusor2{TypeParam()()}; + + // Invoke the copy-assignment operator. + statusor2 = statusor1; + EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); + EXPECT_THAT(statusor1.status(), Eq(statusor2.status())); +} + +// Verify that copy-assignment of a StatusOr with an ok status is working as +// expected. +TYPED_TEST(StatusOrTest, CopyAssignmentOkStatus) { + StatusOr statusor1{TypeParam()()}; + StatusOr statusor2{ + Status(kErrorCode, kErrorMessage)}; + + // Invoke the copy-assignment operator. + statusor2 = statusor1; + EXPECT_THAT(statusor1.ok(), Eq(statusor2.ok())); + ASSERT_THAT(statusor2, IsOk()); + EXPECT_THAT(statusor1.ValueOrDie(), Eq(statusor2.ValueOrDie())); +} + +// Verify that StatusOr can be move-constructed from a StatusOr with a non-ok +// status. +TYPED_TEST(StatusOrTest, MoveConstructorNonOkStatus) { + Status status{kErrorCode, kErrorMessage}; + StatusOr statusor1{status}; + StatusOr statusor2{std::move(statusor1)}; + + // Verify that the status of the donor object was updated. + EXPECT_THAT(statusor1.ok(), IsFalse()); // NOLINT + // NOLINTNEXTLINE + EXPECT_THAT(statusor1.status(), StatusIs(StatusCode::kUnknown, "")); + + // Verify that the destination object contains the status previously held by + // the donor. + EXPECT_THAT(statusor2.ok(), IsFalse()); + EXPECT_THAT(statusor2.status(), Eq(status)); +} + +// Verify that StatusOr can be move-constructed from a StatusOr with an ok +// status. +TYPED_TEST(StatusOrTest, MoveConstructorOkStatus) { + auto value = TypeParam()(); + StatusOr statusor1{value}; + StatusOr statusor2{std::move(statusor1)}; + + // The destination object should possess the value previously held by the + // donor. + ASSERT_THAT(statusor2, IsOk()); + EXPECT_THAT(statusor2.ValueOrDie(), Eq(value)); +} + +// Verify that move-assignment from a StatusOr with a non-ok status is working +// as expected. +TYPED_TEST(StatusOrTest, MoveAssignmentOperatorNonOkStatus) { + Status status(kErrorCode, kErrorMessage); + StatusOr statusor1{status}; + StatusOr statusor2{TypeParam()()}; + + // Invoke the move-assignment operator. + statusor2 = std::move(statusor1); + + // Verify that the status of the donor object was updated. + EXPECT_THAT(statusor1.ok(), IsFalse()); // NOLINT + // NOLINTNEXTLINE + EXPECT_THAT(statusor1.status(), StatusIs(StatusCode::kUnknown, "")); + + // Verify that the destination object contains the status previously held by + // the donor. + EXPECT_THAT(statusor2.ok(), IsFalse()); + EXPECT_THAT(statusor2.status(), Eq(status)); +} + +// Verify that move-assignment from a StatusOr with an ok status is working as +// expected. +TYPED_TEST(StatusOrTest, MoveAssignmentOperatorOkStatus) { + auto value = TypeParam()(); + StatusOr statusor1{value}; + StatusOr statusor2{ + Status{kErrorCode, kErrorMessage}}; + + // Invoke the move-assignment operator. + statusor2 = std::move(statusor1); + + // The destination object should possess the value previously held by the + // donor. + ASSERT_THAT(statusor2, IsOk()); + EXPECT_THAT(statusor2.ValueOrDie(), Eq(value)); +} + +// Verify that the sapi::IsOk() gMock matcher works with StatusOr. +TYPED_TEST(StatusOrTest, IsOkMatcher) { + auto value = TypeParam()(); + StatusOr statusor{value}; + + EXPECT_THAT(statusor, IsOk()); + + statusor = StatusOr{ + Status{kErrorCode, kErrorMessage}}; + EXPECT_THAT(statusor, Not(IsOk())); +} + +// Tests for move-only types. These tests use std::unique_ptr<> as the +// test type, since it is valuable to support this type in the Asylo infra. +// These tests are not part of the typed test suite for the following reasons: +// * std::unique_ptr<> cannot be used as a type in tests that expect +// the test type to support copy operations. +// * std::unique_ptr<> provides an equality operator that checks equality of +// the underlying ptr. Consequently, it is difficult to generalize existing +// tests that verify ValueOrDie() functionality using equality comparisons. + +// Verify that a StatusOr object can be constructed from a move-only type. +TEST(StatusOrTest, InitializationMoveOnlyType) { + std::string* str = new std::string{kStringElement}; + std::unique_ptr value(str); + StatusOr> statusor(std::move(value)); + + ASSERT_THAT(statusor, IsOk()); + EXPECT_THAT(statusor.ValueOrDie().get(), Eq(str)); +} + +// Verify that a StatusOr object can be move-constructed from a move-only type. +TEST(StatusOrTest, MoveConstructorMoveOnlyType) { + std::string* str = new std::string{kStringElement}; + std::unique_ptr value{str}; + StatusOr> statusor1{std::move(value)}; + StatusOr> statusor2{std::move(statusor1)}; + + // The destination object should possess the value previously held by the + // donor. + ASSERT_THAT(statusor2, IsOk()); + EXPECT_THAT(statusor2.ValueOrDie().get(), Eq(str)); +} + +// Verify that a StatusOr object can be move-assigned to from a StatusOr object +// containing a move-only type. +TEST(StatusOrTest, MoveAssignmentMoveOnlyType) { + std::string* str = new std::string{kStringElement}; + std::unique_ptr value{str}; + StatusOr> statusor1(std::move(value)); + StatusOr> statusor2( + Status(kErrorCode, kErrorMessage)); + + // Invoke the move-assignment operator. + statusor2 = std::move(statusor1); + + // The destination object should possess the value previously held by the + // donor. + ASSERT_THAT(statusor2, IsOk()); + EXPECT_THAT(statusor2.ValueOrDie().get(), Eq(str)); +} + +// Verify that a value can be moved out of a StatusOr object via ValueOrDie(). +TEST(StatusOrTest, ValueOrDieMovedValue) { + std::string* str = new std::string{kStringElement}; + std::unique_ptr value{str}; + StatusOr> statusor{std::move(value)}; + + std::unique_ptr moved_value = std::move(statusor).ValueOrDie(); + EXPECT_THAT(moved_value.get(), Eq(str)); + EXPECT_THAT(*moved_value, Eq(kStringElement)); +} + +} // namespace +} // namespace sapi diff --git a/sandboxed_api/var_abstract.cc b/sandboxed_api/var_abstract.cc new file mode 100644 index 0000000..2bfc9ca --- /dev/null +++ b/sandboxed_api/var_abstract.cc @@ -0,0 +1,134 @@ +// 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 sapi::v::Var + +#include "sandboxed_api/var_abstract.h" + +#include + +#include +#include "sandboxed_api/sandbox2/comms.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/rpcchannel.h" +#include "sandboxed_api/util/canonical_errors.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sapi { +namespace v { + +Var::~Var() { + if (free_rpc_channel_ && GetRemote()) { + this->Free(free_rpc_channel_).IgnoreError(); + } +} + +sapi::Status Var::Allocate(RPCChannel* rpc_channel, bool automatic_free) { + void* addr; + SAPI_RETURN_IF_ERROR(rpc_channel->Allocate(GetSize(), &addr)); + + if (!addr) { + LOG(ERROR) << "Allocate: returned nullptr"; + return sapi::UnavailableError("Allocating memory failed"); + } + + SetRemote(addr); + if (automatic_free) { + SetFreeRPCChannel(rpc_channel); + } + + return sapi::OkStatus(); +} + +sapi::Status Var::Free(RPCChannel* rpc_channel) { + SAPI_RETURN_IF_ERROR(rpc_channel->Free(GetRemote())); + + SetRemote(nullptr); + return sapi::OkStatus(); +} + +sapi::Status Var::TransferToSandboxee(RPCChannel* rpc_channel, pid_t pid) { + VLOG(3) << "TransferToSandboxee for: " << ToString() + << ", local: " << GetLocal() << ", remote: " << GetRemote() + << ", size: " << GetSize(); + + if (remote_ == nullptr) { + LOG(WARNING) << "Object: " << GetType() << " has no remote object set"; + return sapi::FailedPreconditionError( + absl::StrCat("Object: ", GetType(), " has no remote object set")); + } + + struct iovec local = { + .iov_base = GetLocal(), + .iov_len = GetSize(), + }; + struct iovec remote = { + .iov_base = GetRemote(), + .iov_len = GetSize(), + }; + + ssize_t ret = process_vm_writev(pid, &local, 1, &remote, 1, 0); + if (ret == -1) { + PLOG(WARNING) << "process_vm_writev(pid: " << pid + << " laddr: " << GetLocal() << " raddr: " << GetRemote() + << " size: " << GetSize() << ")"; + return sapi::UnavailableError("process_vm_writev failed"); + } + if (ret != GetSize()) { + LOG(WARNING) << "process_vm_writev(pid: " << pid << " laddr: " << GetLocal() + << " raddr: " << GetRemote() << " size: " << GetSize() << ")" + << " transferred " << ret << " bytes"; + return sapi::UnavailableError("process_vm_writev: partial success"); + } + + return sapi::OkStatus(); +} + +sapi::Status Var::TransferFromSandboxee(RPCChannel* rpc_channel, pid_t pid) { + VLOG(3) << "TransferFromSandboxee for: " << ToString() + << ", local: " << GetLocal() << ", remote: " << GetRemote() + << ", size: " << GetSize(); + + if (local_ == nullptr) { + return sapi::FailedPreconditionError( + absl::StrCat("Object: ", GetType(), " has no local storage set")); + } + + struct iovec local = { + .iov_base = GetLocal(), + .iov_len = GetSize(), + }; + struct iovec remote = { + .iov_base = GetRemote(), + .iov_len = GetSize(), + }; + + ssize_t ret = process_vm_readv(pid, &local, 1, &remote, 1, 0); + if (ret == -1) { + PLOG(WARNING) << "process_vm_readv(pid: " << pid << " laddr: " << GetLocal() + << " raddr: " << GetRemote() << " size: " << GetSize() << ")"; + return sapi::UnavailableError("process_vm_readv failed"); + } + if (ret != GetSize()) { + LOG(WARNING) << "process_vm_readv(pid: " << pid << " laddr: " << GetLocal() + << " raddr: " << GetRemote() << " size: " << GetSize() << ")" + << " transferred " << ret << " bytes"; + return sapi::UnavailableError("process_vm_readv succeeded partially"); + } + + return sapi::OkStatus(); +} + +} // namespace v +} // namespace sapi diff --git a/sandboxed_api/var_abstract.h b/sandboxed_api/var_abstract.h new file mode 100644 index 0000000..9335477 --- /dev/null +++ b/sandboxed_api/var_abstract.h @@ -0,0 +1,113 @@ +// 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_VAR_ABSTRACT_H_ +#define SANDBOXED_API_VAR_ABSTRACT_H_ + +#include +#include + +#include "absl/base/macros.h" +#include "sandboxed_api/var_type.h" +#include "sandboxed_api/util/status.h" + +namespace sandbox2 { +class Comms; +} + +namespace sapi { +class Sandbox; +class RPCChannel; + +namespace v { + +class Ptr; + +// An abstract class representing variables. +class Var { + public: + Var(const Var&) = delete; + Var& operator=(const Var&) = delete; + + // Returns the address of the storage (remote side). + virtual void* GetRemote() const { return remote_; } + + // Sets the address of the remote storage. + virtual void SetRemote(void* remote) { remote_ = remote; } + + // Returns the address of the storage (local side). + virtual void* GetLocal() const { return local_; } + + // Returns the size of the local variable storage. + virtual size_t GetSize() const = 0; + + // Returns the type of the variable. + virtual Type GetType() const = 0; + + // Returns a std::string representation of the variable type. + virtual std::string GetTypeString() const = 0; + + // Returns a std::string representation of the variable value. + virtual std::string ToString() const = 0; + + virtual ~Var(); + + protected: + Var() : local_(nullptr), remote_(nullptr), free_rpc_channel_(nullptr) {} + + // Set pointer to local storage class. + void SetLocal(void* local) { local_ = local; } + + // Setter/Getter for the address of a Comms object which can be used to + // remotely free allocated memory backing up this variable, upon this object's + // end of life-time + void SetFreeRPCChannel(RPCChannel* rpc_channel) { + free_rpc_channel_ = rpc_channel; + } + RPCChannel* GetFreeRPCChannel() { return free_rpc_channel_; } + + // Allocates the local variable on the remote side. The 'automatic_free' + // argument dictates whether the remote memory should be freed upon end of + // this object's lifetime. + virtual sapi::Status Allocate(RPCChannel* rpc_channel, bool automatic_free); + + // Frees the local variable on the remote side. + virtual sapi::Status Free(RPCChannel* rpc_channel); + + // Transfers the variable to the sandboxee's address space, has to be + // allocated there first. + virtual sapi::Status TransferToSandboxee(RPCChannel* rpc_channel, pid_t pid); + + // Transfers the variable from the sandboxee's address space. + virtual sapi::Status TransferFromSandboxee(RPCChannel* rpc_channel, + pid_t pid); + + private: + // Pointer to local storage of the variable. + void* local_; + // Pointer to remote storage of the variable. + void* remote_; + + // Comms which can be used to free resources allocated in the sandboxer upon + // this process' end of lifetime. + RPCChannel* free_rpc_channel_; + + // Invokes Allocate()/Free()/Transfer*Sandboxee(). + friend class ::sapi::Sandbox; +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_ABSTRACT_H_ diff --git a/sandboxed_api/var_array.h b/sandboxed_api/var_array.h new file mode 100644 index 0000000..363a5de --- /dev/null +++ b/sandboxed_api/var_array.h @@ -0,0 +1,174 @@ +// 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_VAR_ARRAY_H_ +#define SANDBOXED_API_VAR_ARRAY_H_ + +#include +#include + +#include "absl/base/macros.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/rpcchannel.h" +#include "sandboxed_api/var_abstract.h" +#include "sandboxed_api/var_pointable.h" +#include "sandboxed_api/var_ptr.h" +#include "sandboxed_api/util/canonical_errors.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sapi { +namespace v { + +// Class representing an array. +template +class Array : public Var, public Pointable { + public: + // The array is not owned by this object. + Array(T* arr, size_t nelem) + : arr_(arr), + nelem_(nelem), + total_size_(nelem_ * sizeof(T)), + buffer_owned_(false) { + SetLocal(const_cast(reinterpret_cast(arr_))); + } + // The array is allocated and owned by this object. + explicit Array(size_t nelem) + : arr_(static_cast(malloc(sizeof(T) * nelem))), + nelem_(nelem), + total_size_(nelem_ * sizeof(T)), + buffer_owned_(true) { + SetLocal(const_cast(reinterpret_cast(arr_))); + } + virtual ~Array() { + if (buffer_owned_) { + free(const_cast(reinterpret_cast(arr_))); + } + } + + T& operator[](size_t v) const { return arr_[v]; } + T* GetData() const { return arr_; } + + size_t GetNElem() const { return nelem_; } + size_t GetSize() const final { return total_size_; } + Type GetType() const final { return Type::kArray; } + std::string GetTypeString() const final { return "Array"; } + std::string ToString() const override { + return absl::StrCat("Array, elem size: ", sizeof(T), + " B., total size: ", total_size_, + " B., nelems: ", GetNElem()); + } + + Ptr* CreatePtr(Pointable::SyncType type) override { + return new Ptr(this, type); + } + + // Resizes the local and remote buffer using realloc(). Note that this will + // make all pointers to the current data (inside and outside of the sandbox) + // invalid. + sapi::Status Resize(RPCChannel* rpc_channel, size_t nelems) { + size_t absolute_size = sizeof(T) * nelems; + // Resize local buffer. + SAPI_RETURN_IF_ERROR(EnsureOwnedLocalBuffer(absolute_size)); + + // Resize remote buffer and update local pointer. + void* new_addr; + + SAPI_RETURN_IF_ERROR( + rpc_channel->Reallocate(GetRemote(), absolute_size, &new_addr)); + if (!new_addr) { + return sapi::UnavailableError("Reallocate() returned nullptr"); + } + SetRemote(new_addr); + return sapi::OkStatus(); + } + + private: + // Resizes the internal storage. + sapi::Status EnsureOwnedLocalBuffer(size_t size) { + if (size % sizeof(T)) { + return sapi::FailedPreconditionError( + "Array size not a multiple of the item size"); + } + // Do not (re-)allocate memory if the new size matches our size - except + // when we don't own that buffer. + if (size == total_size_ && buffer_owned_) { + return sapi::OkStatus(); + } + void* new_addr = nullptr; + if (buffer_owned_) { + new_addr = realloc(arr_, size); + } else { + new_addr = malloc(size); + if (new_addr) { + memcpy(new_addr, arr_, total_size_); + buffer_owned_ = true; + } + } + if (!new_addr) { + return sapi::UnavailableError("(Re-)malloc failed"); + } + + arr_ = static_cast(new_addr); + total_size_ = size; + nelem_ = size / sizeof(T); + SetLocal(arr_); + return sapi::OkStatus(); + } + + // Pointer to the data, owned by the object if buffer_owned_ is 'true'. + T* arr_; + // Number of elements. + size_t nelem_; + // Total size in bytes. + size_t total_size_; + // Do we own the buffer? + bool buffer_owned_; + + friend class LenVal; +}; + +// Specialized Array class for representing NUL-terminated C-style strings. The +// buffer is owned by the class, and is mutable. +class CStr : public Array { + public: + explicit CStr(char* cstr) : Array(strlen(cstr) + 1) { + strcpy(this->GetData(), cstr); // NOLINT + } + + std::string ToString() const final { + return absl::StrCat("CStr: len(w/o NUL):", strlen(GetData()), ", ['", + GetData(), "']"); + } +}; + +// Specialized Array class for representing NUL-terminated C-style strings. The +// buffer is not owned by the class and is not mutable. +class ConstCStr : public Array { + public: + explicit ConstCStr(const char* cstr) + : Array(cstr, strlen(cstr) + 1) {} + + std::string ToString() const final { + if (GetData() == nullptr) { + return "CStr: [nullptr]"; + } + return absl::StrCat("CStr: len(w/o NUL):", strlen(GetData()), ", ['", + GetData(), "']"); + } +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_ARRAY_H_ diff --git a/sandboxed_api/var_int.cc b/sandboxed_api/var_int.cc new file mode 100644 index 0000000..db739b9 --- /dev/null +++ b/sandboxed_api/var_int.cc @@ -0,0 +1,96 @@ +// 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/var_int.h" +#include "sandboxed_api/rpcchannel.h" +#include "sandboxed_api/util/canonical_errors.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sapi { +namespace v { + +Fd::~Fd() { + if (GetFreeRPCChannel() && GetRemoteFd() >= 0 && own_remote_) { + this->CloseRemoteFd(GetFreeRPCChannel()).IgnoreError(); + } + if (GetValue() >= 0 && own_local_) { + CloseLocalFd(); + } +} + +sapi::Status Fd::CloseRemoteFd(RPCChannel* rpc_channel) { + SAPI_RETURN_IF_ERROR(rpc_channel->Close(GetRemoteFd())); + + SetRemoteFd(-1); + return sapi::OkStatus(); +} + +void Fd::CloseLocalFd() { + if (GetValue() < 0) { + return; + } + if (close(GetValue()) != 0) { + PLOG(WARNING) << "close(" << GetValue() << ") failed"; + } + + SetValue(-1); +} + +sapi::Status Fd::TransferToSandboxee(RPCChannel* rpc_channel, pid_t /* pid */) { + int remote_fd; + + SetFreeRPCChannel(rpc_channel); + OwnRemoteFd(true); + + if (GetValue() < 0) { + return sapi::FailedPreconditionError( + "Cannot transfer FD: Local FD not valid"); + } + + if (GetRemoteFd() >= 0) { + return sapi::FailedPreconditionError( + "Cannot transfer FD: Sandboxee already has a valid FD"); + } + + SAPI_RETURN_IF_ERROR(rpc_channel->SendFD(GetValue(), &remote_fd)); + SetRemoteFd(remote_fd); + + return sapi::OkStatus(); +} + +sapi::Status Fd::TransferFromSandboxee(RPCChannel* rpc_channel, + pid_t /* pid */) { + int local_fd; + + SetFreeRPCChannel(rpc_channel); + OwnRemoteFd(false); + + if (GetValue()) { + return sapi::FailedPreconditionError( + "Cannot transfer FD back: Our FD is already valid"); + } + + if (GetRemoteFd() < 0) { + return sapi::FailedPreconditionError( + "Cannot transfer FD back: Sandboxee has no valid FD"); + } + + SAPI_RETURN_IF_ERROR(rpc_channel->RecvFD(GetRemoteFd(), &local_fd)); + SetValue(local_fd); + + return sapi::OkStatus(); +} + +} // namespace v +} // namespace sapi diff --git a/sandboxed_api/var_int.h b/sandboxed_api/var_int.h new file mode 100644 index 0000000..2197bee --- /dev/null +++ b/sandboxed_api/var_int.h @@ -0,0 +1,107 @@ +// 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_VAR_INT_H_ +#define SANDBOXED_API_VAR_INT_H_ + +#include + +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/var_pointable.h" +#include "sandboxed_api/var_ptr.h" +#include "sandboxed_api/var_reg.h" + +namespace sapi { +namespace v { + +// Intermediate class for register sized variables +// so we don't have to implement ptr() everywhere. +template +class IntBase : public Reg, public Pointable { + public: + IntBase() : IntBase(static_cast(0)) {} + explicit IntBase(T value) { this->SetValue(value); } + + Ptr* CreatePtr(Pointable::SyncType type = Pointable::SYNC_NONE) override { + return new Ptr(this, type); + } +}; + +using Bool = IntBase; +using Char = IntBase; +using UChar = IntBase; +using SChar = IntBase; + +using Short = IntBase; // NOLINT +using UShort = IntBase; // NOLINT +using SShort = IntBase; // NOLINT + +using Int = IntBase; +using UInt = IntBase; +using SInt = IntBase; + +using Long = IntBase; // NOLINT +using ULong = IntBase; // NOLINT +using SLong = IntBase; // NOLINT +using LLong = IntBase; // NOLINT +using ULLong = IntBase; // NOLINT +using SLLong = IntBase; // NOLINT + +class GenericPtr : public IntBase { + public: + GenericPtr() { SetValue(0); } + explicit GenericPtr(uintptr_t val) { SetValue(val); } + explicit GenericPtr(void* val) { SetValue(reinterpret_cast(val)); } +}; + +class Fd : public Int { + public: + Type GetType() const override { return Type::kFd; } + explicit Fd(int val) : remote_fd_(-1), own_local_(true), own_remote_(true) { + SetValue(val); + } + ~Fd() override; + + // Getter and setter of remote file descriptor. + void SetRemoteFd(int remote_fd) { remote_fd_ = remote_fd; } + int GetRemoteFd() { return remote_fd_; } + + // Sets remote and local fd ownership, true by default. + // Owned fd will be closed during object destruction. + void OwnRemoteFd(bool owned) { own_remote_ = owned; } + void OwnLocalFd(bool owned) { own_local_ = owned; } + + // Close remote fd in the sadboxee. + sapi::Status CloseRemoteFd(RPCChannel* rpc_channel); + // Close local fd. + void CloseLocalFd(); + + protected: + // Sends local fd to sandboxee, takes ownership of the fd. + sapi::Status TransferFromSandboxee(RPCChannel* rpc_channel, + pid_t pid) override; + + // Retrieves remote file descriptor, does not own fd. + sapi::Status TransferToSandboxee(RPCChannel* rpc_channel, pid_t pid) override; + + private: + int remote_fd_; + bool own_local_; + bool own_remote_; +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_INT_H_ diff --git a/sandboxed_api/var_lenval.cc b/sandboxed_api/var_lenval.cc new file mode 100644 index 0000000..7aa6ec5 --- /dev/null +++ b/sandboxed_api/var_lenval.cc @@ -0,0 +1,73 @@ +// 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 sapi::v::LenVal. + +#include "sandboxed_api/var_lenval.h" + +#include + +#include +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/rpcchannel.h" + +namespace sapi { +namespace v { + +sapi::Status LenVal::Allocate(RPCChannel* rpc_channel, bool automatic_free) { + SAPI_RETURN_IF_ERROR(struct_.Allocate(rpc_channel, automatic_free)); + SAPI_RETURN_IF_ERROR(array_.Allocate(rpc_channel, true)); + + // Set data pointer. + struct_.mutable_data()->data = array_.GetRemote(); + return sapi::OkStatus(); +} + +sapi::Status LenVal::Free(RPCChannel* rpc_channel) { + SAPI_RETURN_IF_ERROR(array_.Free(rpc_channel)); + SAPI_RETURN_IF_ERROR(struct_.Free(rpc_channel)); + return sapi::OkStatus(); +} + +sapi::Status LenVal::TransferToSandboxee(RPCChannel* rpc_channel, pid_t pid) { + // Sync the structure and the underlying array. + SAPI_RETURN_IF_ERROR(struct_.TransferToSandboxee(rpc_channel, pid)); + SAPI_RETURN_IF_ERROR(array_.TransferToSandboxee(rpc_channel, pid)); + return sapi::OkStatus(); +} + +sapi::Status LenVal::TransferFromSandboxee(RPCChannel* rpc_channel, pid_t pid) { + // Sync the structure back. + SAPI_RETURN_IF_ERROR(struct_.TransferFromSandboxee(rpc_channel, pid)); + + // Resize the local array if required. Also make sure we own the buffer, this + // is the only way we can be sure that the buffer is writable. + size_t new_size = struct_.data().size; + SAPI_RETURN_IF_ERROR(array_.EnsureOwnedLocalBuffer(new_size)); + + // Remote pointer might have changed, update it. + array_.SetRemote(struct_.data().data); + return array_.TransferFromSandboxee(rpc_channel, pid); +} + +sapi::Status LenVal::ResizeData(RPCChannel* rpc_channel, size_t size) { + SAPI_RETURN_IF_ERROR(array_.Resize(rpc_channel, size)); + auto* struct_data = struct_.mutable_data(); + struct_data->data = array_.GetRemote(); + struct_data->size = size; + return sapi::OkStatus(); +} + +} // namespace v +} // namespace sapi diff --git a/sandboxed_api/var_lenval.h b/sandboxed_api/var_lenval.h new file mode 100644 index 0000000..3b7d038 --- /dev/null +++ b/sandboxed_api/var_lenval.h @@ -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. + +#ifndef SANDBOXED_API_VAR_LENVAL_H_ +#define SANDBOXED_API_VAR_LENVAL_H_ + +#include + +#include + +#include "absl/base/macros.h" +#include "sandboxed_api/lenval_core.h" +#include "sandboxed_api/var_abstract.h" +#include "sandboxed_api/var_array.h" +#include "sandboxed_api/var_pointable.h" +#include "sandboxed_api/var_ptr.h" +#include "sandboxed_api/var_struct.h" + +namespace sapi { +namespace v { + +template +class Proto; + +// Length + value container. Represents a pointer to a LenValStruct inside the +// sandboxee which allows the bidirectional synchronization data structures with +// changing lengths (e.g. protobuf structures). You probably want to directly +// use protobufs as they are easier to handle. +class LenVal : public Var, public Pointable { + public: + explicit LenVal(const char* data, uint64_t size) + : array_(const_cast(reinterpret_cast(data)), + size), + struct_(size, nullptr) {} + + explicit LenVal(const std::vector& data) + : array_(data.size()), struct_(data.size(), nullptr) { + memcpy(array_.GetData(), data.data(), data.size()); + } + + explicit LenVal(size_t size) : array_(size), struct_(size, nullptr) {} + + Type GetType() const final { return Type::kLenVal; } + std::string GetTypeString() const final { return "LengthValue"; } + std::string ToString() const final { return "LenVal"; } + + Ptr* CreatePtr(Pointable::SyncType type) override { + return new Ptr(this, type); + } + + sapi::Status ResizeData(RPCChannel* rpc_channel, size_t size); + size_t GetDataSize() const { return struct_.data().size; } + uint8_t* GetData() const { return array_.GetData(); } + void* GetRemote() const final { return struct_.GetRemote(); } + + protected: + size_t GetSize() const final { return 0; } + sapi::Status Allocate(RPCChannel* rpc_channel, bool automatic_free) override; + sapi::Status Free(RPCChannel* rpc_channel) override; + sapi::Status TransferToSandboxee(RPCChannel* rpc_channel, pid_t pid) override; + sapi::Status TransferFromSandboxee(RPCChannel* rpc_channel, + pid_t pid) override; + + Array array_; + Struct struct_; + + template + friend class Proto; +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_LENVAL_H_ diff --git a/sandboxed_api/var_pointable.cc b/sandboxed_api/var_pointable.cc new file mode 100644 index 0000000..f5a8b96 --- /dev/null +++ b/sandboxed_api/var_pointable.cc @@ -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. + +#include "sandboxed_api/var_pointable.h" +#include "sandboxed_api/var_ptr.h" + +namespace sapi { +namespace v { + +void PtrDeleter::operator()(Ptr *p) { delete p; } + +} // namespace v +} // namespace sapi diff --git a/sandboxed_api/var_pointable.h b/sandboxed_api/var_pointable.h new file mode 100644 index 0000000..f8820b5 --- /dev/null +++ b/sandboxed_api/var_pointable.h @@ -0,0 +1,99 @@ +// 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_VAR_POINTABLE_H_ +#define SANDBOXED_API_VAR_POINTABLE_H_ + +#include + +#include "sandboxed_api/var_reg.h" + +namespace sapi { +namespace v { + +class Ptr; + +// Needed so that we can use unique_ptr with incomplete type. +struct PtrDeleter { + void operator()(Ptr* p); +}; + +// Class that implements pointer support for different objects. +class Pointable { + public: + enum SyncType { + // Do not synchronize the underlying object after/before calls. + SYNC_NONE = 0x0, + // Synchronize the underlying object (send the data to the sandboxee) + // before the call takes place. + SYNC_BEFORE = 0x1, + // Synchronize the underlying object (retrieve data from the sandboxee) + // after the call has finished. + SYNC_AFTER = 0x2, + // Synchronize the underlying object with the remote object, by sending the + // data to the sandboxee before the call, and retrieving it from the + // sandboxee after the call has finished. + SYNC_BOTH = SYNC_BEFORE | SYNC_AFTER, + }; + + // Necessary to implement creation of Ptr in inheriting class as it is + // incomplete type here. + virtual Ptr* CreatePtr(SyncType type) = 0; + + // Functions to get pointers with certain type of synchronization scheme. + Ptr* PtrNone() { + if (ptr_none_ == nullptr) { + ptr_none_.reset(CreatePtr(SYNC_NONE)); + } + + return ptr_none_.get(); + } + + Ptr* PtrBoth() { + if (ptr_both_ == nullptr) { + ptr_both_.reset(CreatePtr(SYNC_BOTH)); + } + + return ptr_both_.get(); + } + + Ptr* PtrBefore() { + if (ptr_before_ == nullptr) { + ptr_before_.reset(CreatePtr(SYNC_BEFORE)); + } + + return ptr_before_.get(); + } + + Ptr* PtrAfter() { + if (ptr_after_ == nullptr) { + ptr_after_.reset(CreatePtr(SYNC_AFTER)); + } + + return ptr_after_.get(); + } + + virtual ~Pointable() = default; + + private: + std::unique_ptr ptr_none_; + std::unique_ptr ptr_both_; + std::unique_ptr ptr_before_; + std::unique_ptr ptr_after_; +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_POINTABLE_H_ diff --git a/sandboxed_api/var_proto.h b/sandboxed_api/var_proto.h new file mode 100644 index 0000000..521cdfa --- /dev/null +++ b/sandboxed_api/var_proto.h @@ -0,0 +1,95 @@ +// 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. + +// Provides a class to marshall protobufs in and out of the sandbox + +#ifndef SANDBOXED_API_VAR_PROTO_H_ +#define SANDBOXED_API_VAR_PROTO_H_ + +#include + +#include "sandboxed_api/proto_helper.h" +#include "sandboxed_api/var_lenval.h" +#include "sandboxed_api/var_pointable.h" +#include "sandboxed_api/var_ptr.h" + +namespace sapi { +namespace v { + +template +class Proto : public Pointable, public Var { + public: + explicit Proto(const T& proto) : wrapped_var_(SerializeProto(proto)) {} + + size_t GetSize() const final { return wrapped_var_.GetSize(); } + Type GetType() const final { return Type::kProto; } + std::string GetTypeString() const final { return "Protobuf"; } + std::string ToString() const final { return "Protobuf"; } + + Ptr* CreatePtr(Pointable::SyncType type) override { + return new Ptr(this, type); + } + + void* GetRemote() const override { return wrapped_var_.GetRemote(); } + void* GetLocal() const override { return wrapped_var_.GetLocal(); } + + // Returns a copy of the stored protobuf object. + std::unique_ptr GetProtoCopy() const { + auto res = absl::make_unique(); + if (!res || + !DeserializeProto(res.get(), + reinterpret_cast(wrapped_var_.GetData()), + wrapped_var_.GetDataSize())) { + res.reset(); + } + return res; + } + + void SetRemote(void* remote) override { + // We do not support that much indirection (pointer to a pointer to a + // protobuf) as it is unlikely that this is wanted behavior. If you expect + // this to work, please get in touch with us. + LOG(FATAL) << "SetRemote not supported on protobufs."; + } + + protected: + // Forward a couple of function calls to the actual var. + sapi::Status Allocate(RPCChannel* rpc_channel, bool automatic_free) override { + return wrapped_var_.Allocate(rpc_channel, automatic_free); + } + + sapi::Status Free(RPCChannel* rpc_channel) override { + return sapi::OkStatus(); + } + + sapi::Status TransferToSandboxee(RPCChannel* rpc_channel, + pid_t pid) override { + return wrapped_var_.TransferToSandboxee(rpc_channel, pid); + } + + sapi::Status TransferFromSandboxee(RPCChannel* rpc_channel, + pid_t pid) override { + return wrapped_var_.TransferFromSandboxee(rpc_channel, pid); + } + + private: + // The management of reading/writing the data to the sandboxee is handled by + // the LenVal class. + LenVal wrapped_var_; +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_PROTO_H_ diff --git a/sandboxed_api/var_ptr.h b/sandboxed_api/var_ptr.h new file mode 100644 index 0000000..644c959 --- /dev/null +++ b/sandboxed_api/var_ptr.h @@ -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. + +#ifndef SANDBOXED_API_VAR_PTR_H_ +#define SANDBOXED_API_VAR_PTR_H_ + +#include + +#include "absl/base/macros.h" +#include "absl/strings/str_format.h" +#include "sandboxed_api/var_pointable.h" +#include "sandboxed_api/var_reg.h" + +namespace sapi { +namespace v { + +// Class representing a pointer. Takes both Var* and regular pointers in the +// initializers. +class Ptr : public Reg, public Pointable { + public: + Ptr() = delete; + + explicit Ptr(Var* val, SyncType sync_type) + : sync_type_(sync_type) { + Reg::SetValue(val); + } + + Var* GetPointedVar() const { return Reg::GetValue(); } + + void SetValue(Var* ptr) final { val_->SetRemote(ptr); } + Var* GetValue() const final { + return reinterpret_cast(val_->GetRemote()); + } + + const void* GetDataPtr() final { + remote_ptr_cache_ = GetValue(); + return &remote_ptr_cache_; + } + void SetDataFromPtr(const void* ptr, size_t max_sz) final { + void* tmp; + memcpy(&tmp, ptr, std::min(sizeof(tmp), max_sz)); + SetValue(reinterpret_cast(tmp)); + } + + // Getter/Setter for the sync_type_ field. + SyncType GetSyncType() { return sync_type_; } + void SetSyncType(SyncType sync_type) { sync_type_ = sync_type; } + + std::string ToString() const final { + return absl::StrFormat( + "Ptr to obj:%p (type:'%s' val:'%s'), local:%p, remote:%p, size:%tx", + GetPointedVar(), GetPointedVar()->GetTypeString(), + GetPointedVar()->ToString(), GetPointedVar()->GetLocal(), + GetPointedVar()->GetRemote(), GetPointedVar()->GetSize()); + } + + Ptr* CreatePtr(Pointable::SyncType type = Pointable::SYNC_NONE) override { + return new Ptr(this, type); + } + + private: + // GetDataPtr() interface requires of us to return a pointer to the data + // (variable) that can be copied. We cannot get pointer to pointer with + // Var::GetRemote(), hence we cache it, and return pointer to it. + void* remote_ptr_cache_; + + // Shall we synchronize the underlying object before/after call. + SyncType sync_type_; +}; + +// Good, old nullptr +class NullPtr : public Ptr { + public: + NullPtr() : Ptr(&void_obj_, SyncType::SYNC_NONE) {} + + private: + Reg void_obj_; +}; + +// Pointer, which can only point to remote memory, and is never synchronized. +class RemotePtr : public Ptr { + public: + explicit RemotePtr(void* remote_addr) + : Ptr(&pointed_obj_, SyncType::SYNC_NONE) { + pointed_obj_.SetRemote(remote_addr); + } + + private: + Reg pointed_obj_; +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_PTR_H_ diff --git a/sandboxed_api/var_reg.h b/sandboxed_api/var_reg.h new file mode 100644 index 0000000..bb33375 --- /dev/null +++ b/sandboxed_api/var_reg.h @@ -0,0 +1,183 @@ +// 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_VAR_REG_H_ +#define SANDBOXED_API_VAR_REG_H_ + +#include +#include + +#include +#include "sandboxed_api/var_abstract.h" + +namespace sapi { +namespace v { + +// The super-class for Reg. Specified as a class, so it can be used as a +// type specifier in methods. +class Callable : public Var { + public: + Callable(const Callable&) = delete; + Callable& operator=(const Callable&) = delete; + + // Get pointer to the stored data. + virtual const void* GetDataPtr() = 0; + // Set internal data from ptr. + virtual void SetDataFromPtr(const void* ptr, size_t max_sz) = 0; + // Get data from inernal ptr. + void GetDataFromPtr(void* ptr, size_t max_sz) { + size_t min_sz = std::min(GetSize(), max_sz); + memcpy(ptr, GetDataPtr(), min_sz); + } + + protected: + Callable() = default; +}; + +// class Reg represents register-sized variables. +template +class Reg : public Callable { + static_assert(std::is_integral() || std::is_floating_point() || + std::is_pointer() || std::is_enum(), + "Only register-sized types are allowed as template argument " + "for class Reg."); + + public: + Reg() : Reg(static_cast(0)) {} + + explicit Reg(const T val) { + val_ = val; + SetLocal(&val_); + } + + // Getter/Setter for the stored value. + virtual T GetValue() const { return val_; } + virtual void SetValue(T val) { val_ = val; } + + const void* GetDataPtr() override { + return reinterpret_cast(&val_); + } + void SetDataFromPtr(const void* ptr, size_t max_sz) override { + memcpy(&val_, ptr, std::min(GetSize(), max_sz)); + } + + size_t GetSize() const override { return sizeof(T); } + + Type GetType() const override { + if (std::is_integral() || std::is_enum()) { + return Type::kInt; + } + if (std::is_floating_point()) { + return Type::kFloat; + } + if (std::is_pointer()) { + return Type::kPointer; + } + LOG(FATAL) << "Incorrect type"; + } + + std::string GetTypeString() const override { + if (std::is_integral() || std::is_enum()) { + return "Integer"; + } + if (std::is_floating_point()) { + return "Floating-point"; + } + if (std::is_pointer()) { + return "Pointer"; + } + LOG(FATAL) << "Incorrect type"; + } + + std::string ToString() const override { return ValToString(); } + + protected: + // The stored value. + T val_; + + private: + std::string ValToString() const { + char buf[32]; + bool signd = true; + if (std::is_integral::value || std::is_enum::value) { + if (std::is_integral::value) { + signd = std::is_signed::value; + } + + switch (sizeof(T)) { + case 1: + if (signd) { + snprintf(buf, sizeof(buf), "%" PRId8, + *(reinterpret_cast(&val_))); + } else { + snprintf(buf, sizeof(buf), "%" PRIu8, + *(reinterpret_cast(&val_))); + } + break; + case 2: + if (signd) { + snprintf(buf, sizeof(buf), "%" PRId16, + *(reinterpret_cast(&val_))); + } else { + snprintf(buf, sizeof(buf), "%" PRIu16, + *(reinterpret_cast(&val_))); + } + break; + case 4: + if (signd) { + snprintf(buf, sizeof(buf), "%" PRId32, + *(reinterpret_cast(&val_))); + } else { + snprintf(buf, sizeof(buf), "%" PRIu32, + *(reinterpret_cast(&val_))); + } + break; + case 8: + if (signd) { + snprintf(buf, sizeof(buf), "%" PRId64, + *(reinterpret_cast(&val_))); + } else { + snprintf(buf, sizeof(buf), "%" PRIu64, + *(reinterpret_cast(&val_))); + } + break; + default: + LOG(FATAL) << "Incorrect type"; + break; + } + } else if (std::is_floating_point::value) { + if (std::is_same::value) { + snprintf(buf, sizeof(buf), "%.10f", + *(reinterpret_cast(&val_))); + } else if (std::is_same::value) { + snprintf(buf, sizeof(buf), "%.10lf", + *(reinterpret_cast(&val_))); + } else if (std::is_same::value) { + snprintf(buf, sizeof(buf), "%.10Lf", + *(reinterpret_cast(&val_))); + } + } else if (std::is_pointer::value) { + snprintf(buf, sizeof(buf), "%p", *reinterpret_cast(&val_)); + } else { + LOG(FATAL) << "Incorrect type"; + } + + return std::string(buf); + } +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_REG_H_ diff --git a/sandboxed_api/var_struct.h b/sandboxed_api/var_struct.h new file mode 100644 index 0000000..0476b15 --- /dev/null +++ b/sandboxed_api/var_struct.h @@ -0,0 +1,61 @@ +// 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_VAR_STRUCT_H_ +#define SANDBOXED_API_VAR_STRUCT_H_ + +#include + +#include "absl/base/macros.h" +#include "absl/strings/str_cat.h" +#include "sandboxed_api/var_pointable.h" +#include "sandboxed_api/var_ptr.h" + +namespace sapi { +namespace v { + +// Class representing a structure. +template +class Struct : public Var, public Pointable { + public: + // Forwarding constructor to initalize the struct_ field. + template + explicit Struct(Args&&... args) : struct_(std::forward(args)...) { + SetLocal(&struct_); + } + + size_t GetSize() const final { return sizeof(struct_); } + Type GetType() const final { return Type::kStruct; } + std::string GetTypeString() const final { return "Structure"; } + std::string ToString() const final { + return absl::StrCat("Structure of size: ", sizeof(struct_)); + } + + const T& data() const { return struct_; } + T* mutable_data() { return &struct_; } + + Ptr* CreatePtr(Pointable::SyncType type) override { + return new Ptr(this, type); + } + + protected: + T struct_; + + friend class LenVal; +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_STRUCT_H_ diff --git a/sandboxed_api/var_type.h b/sandboxed_api/var_type.h new file mode 100644 index 0000000..e1f254f --- /dev/null +++ b/sandboxed_api/var_type.h @@ -0,0 +1,42 @@ +// 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_TYPE_H_ +#define SANDBOXED_API_TYPE_H_ + +#include + +namespace sapi { +namespace v { + +enum class Type { + kVoid, // Void + kInt, // Intergal types + kFloat, // Floating-point types + kPointer, // Pointer to an arbitrary data type + kStruct, // Structures + kArray, // Arrays, memory buffers + kFd, // File descriptors/handles + kProto, // Protocol buffers + kLenVal, // Dynamic buffers +}; + +inline std::ostream& operator<<(std::ostream& os, const Type& type) { + return os << static_cast(type); +} + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_TYPE_H_ diff --git a/sandboxed_api/var_void.h b/sandboxed_api/var_void.h new file mode 100644 index 0000000..2a3779c --- /dev/null +++ b/sandboxed_api/var_void.h @@ -0,0 +1,50 @@ +// 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_VAR_VOID_H_ +#define SANDBOXED_API_VAR_VOID_H_ + +#include + +#include "sandboxed_api/var_ptr.h" +#include "sandboxed_api/var_reg.h" + +namespace sapi { +namespace v { + +// Good, old void. +class Void : public Callable, public Pointable { + public: + Void() = default; + + Void(const Void&) = delete; + Void& operator=(const Void&) = delete; + + size_t GetSize() const final { return 0U; } + Type GetType() const final { return Type::kVoid; } + std::string GetTypeString() const final { return "Void"; } + std::string ToString() const final { return "Void"; } + + const void* GetDataPtr() override { return nullptr; } + void SetDataFromPtr(const void* ptr, size_t max_sz) override {} + + Ptr* CreatePtr(Pointable::SyncType type = Pointable::SYNC_NONE) override { + return new Ptr(this, type); + } +}; + +} // namespace v +} // namespace sapi + +#endif // SANDBOXED_API_VAR_VOID_H_ diff --git a/sandboxed_api/vars.h b/sandboxed_api/vars.h new file mode 100644 index 0000000..239123f --- /dev/null +++ b/sandboxed_api/vars.h @@ -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. + +#ifndef SANDBOXED_API_VARS_H_ +#define SANDBOXED_API_VARS_H_ + +#include "sandboxed_api/var_array.h" +#include "sandboxed_api/var_int.h" +#include "sandboxed_api/var_lenval.h" +#include "sandboxed_api/var_pointable.h" +#include "sandboxed_api/var_proto.h" +#include "sandboxed_api/var_ptr.h" +#include "sandboxed_api/var_struct.h" +#include "sandboxed_api/var_void.h" + +#endif // SANDBOXED_API_VARS_H_