From ef7592cfddf227a5a5138bf4e85cc8680aab1b2f Mon Sep 17 00:00:00 2001 From: bielec Date: Tue, 23 Jul 2019 04:40:47 -0700 Subject: [PATCH] Now the network proxy client can automatically redirect connect syscalls to a handler that will send the data (syscall arguments) to the proxy server automatically and will return the obtained socket from the proxy server, in the future rules like allowed IP, protocols, etc. will be added PiperOrigin-RevId: 259512665 Change-Id: I2747c7548ab24c7d2c90abb303fd783c11fed6f4 --- sandboxed_api/sandbox2/BUILD.bazel | 2 + sandboxed_api/sandbox2/client.cc | 13 + sandboxed_api/sandbox2/client.h | 4 + .../sandbox2/examples/CMakeLists.txt | 2 + .../sandbox2/examples/network/BUILD.bazel | 52 ++++ .../sandbox2/examples/network/CMakeLists.txt | 44 ++++ .../sandbox2/examples/network/network_bin.cc | 78 ++++++ .../examples/network/network_sandbox.cc | 237 ++++++++++++++++++ .../examples/network_proxy/BUILD.bazel | 49 ++++ .../examples/network_proxy/CMakeLists.txt | 45 ++++ .../network_proxy/networkproxy_bin.cc | 117 +++++++++ .../network_proxy/networkproxy_sandbox.cc | 176 +++++++++++++ .../sandbox2/network_proxy_client.cc | 127 +++++++++- sandboxed_api/sandbox2/network_proxy_client.h | 20 ++ .../sandbox2/network_proxy_server.cc | 39 +-- sandboxed_api/sandbox2/network_proxy_server.h | 10 +- sandboxed_api/sandbox2/policybuilder.cc | 115 +++++++-- sandboxed_api/sandbox2/policybuilder.h | 7 + 18 files changed, 1091 insertions(+), 46 deletions(-) create mode 100644 sandboxed_api/sandbox2/examples/network/BUILD.bazel create mode 100644 sandboxed_api/sandbox2/examples/network/CMakeLists.txt create mode 100644 sandboxed_api/sandbox2/examples/network/network_bin.cc create mode 100644 sandboxed_api/sandbox2/examples/network/network_sandbox.cc create mode 100644 sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel create mode 100644 sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt create mode 100644 sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc create mode 100644 sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index c478dfa..d3cd0a7 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -148,6 +148,7 @@ cc_library( ":comms", "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:status", + "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_glog//:glog", @@ -366,6 +367,7 @@ cc_library( "//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:raw_logging", + "//sandboxed_api/util:status", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", diff --git a/sandboxed_api/sandbox2/client.cc b/sandboxed_api/sandbox2/client.cc index 6c38eb7..f0c7856 100644 --- a/sandboxed_api/sandbox2/client.cc +++ b/sandboxed_api/sandbox2/client.cc @@ -40,9 +40,11 @@ #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" #include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/network_proxy_client.h" #include "sandboxed_api/sandbox2/sanitizer.h" #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/raw_logging.h" +#include "sandboxed_api/util/canonical_errors.h" namespace sandbox2 { @@ -256,4 +258,15 @@ NetworkProxyClient* Client::GetNetworkProxyClient() { return proxy_client_.get(); } +sapi::Status Client::InstallNetworkProxyHandler() { + if (fd_map_.find(NetworkProxyClient::kFDName) == fd_map_.end()) { + return sapi::FailedPreconditionError( + "InstallNetworkProxyHandler() must be called at most once after the " + "sandbox is installed. Also, the NetworkProxyServer needs to be " + "enabled."); + } + return NetworkProxyHandler::InstallNetworkProxyHandler( + GetNetworkProxyClient()); +} + } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/client.h b/sandboxed_api/sandbox2/client.h index c395d11..5fad08b 100644 --- a/sandboxed_api/sandbox2/client.h +++ b/sandboxed_api/sandbox2/client.h @@ -58,6 +58,10 @@ class Client { // for the first time. NetworkProxyClient* GetNetworkProxyClient(); + // Redirects the connect() syscall to the ConnectHandler() method in + // the NetworkProxyClient class. + sapi::Status InstallNetworkProxyHandler(); + protected: // Comms used for synchronization with the monitor, not owned by the object. Comms* comms_; diff --git a/sandboxed_api/sandbox2/examples/CMakeLists.txt b/sandboxed_api/sandbox2/examples/CMakeLists.txt index 7cbd1f5..dcd9d8c 100644 --- a/sandboxed_api/sandbox2/examples/CMakeLists.txt +++ b/sandboxed_api/sandbox2/examples/CMakeLists.txt @@ -15,6 +15,8 @@ if(SAPI_ENABLE_EXAMPLES) add_subdirectory(crc4) add_subdirectory(custom_fork) + add_subdirectory(network) + add_subdirectory(network_proxy) add_subdirectory(static) add_subdirectory(tool) add_subdirectory(zlib) diff --git a/sandboxed_api/sandbox2/examples/network/BUILD.bazel b/sandboxed_api/sandbox2/examples/network/BUILD.bazel new file mode 100644 index 0000000..fd7dd06 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/network/BUILD.bazel @@ -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. + +# The 'network' example demonstrates: +# - separate executor and sandboxee +# - sandboxee enables sandboxing by calling SandboxMeHere() +# - strict syscall policy +# - sandbox2::Comms for data exchange (IPC) + +licenses(["notice"]) # Apache 2.0 + +load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts") + +# Executor +cc_binary( + name = "network_sandbox", + srcs = ["network_sandbox.cc"], + copts = sapi_platform_copts(), + data = [":network_bin"], + deps = [ + "//sandboxed_api/sandbox2", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2/util:bpf_helper", + "//sandboxed_api/sandbox2/util:fileops", + "//sandboxed_api/sandbox2/util:runfiles", + "//sandboxed_api/util:flags", + ], +) + +# Sandboxee +cc_binary( + name = "network_bin", + srcs = ["network_bin.cc"], + copts = sapi_platform_copts(), + deps = [ + "//sandboxed_api/sandbox2:client", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2:util", + "@com_google_absl//absl/strings:str_format", + ], +) diff --git a/sandboxed_api/sandbox2/examples/network/CMakeLists.txt b/sandboxed_api/sandbox2/examples/network/CMakeLists.txt new file mode 100644 index 0000000..bb03c56 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/network/CMakeLists.txt @@ -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. + +# sandboxed_api/sandbox2/examples/network:network_sandbox +add_executable(sandbox2_network_sandbox + network_sandbox.cc +) +add_executable(sandbox2::network_sandbox ALIAS sandbox2_network_sandbox) +add_dependencies(sandbox2_network_sandbox + sandbox2::network_bin +) +target_link_libraries(sandbox2_network_sandbox PRIVATE + glog::glog + sandbox2::bpf_helper + sandbox2::comms + sandbox2::fileops + sandbox2::runfiles + sandbox2::sandbox2 + sapi::base +) + +# sandboxed_api/sandbox2/examples/network_bin:network_bin +add_executable(network_bin + network_bin.cc +) +add_executable(sandbox2::network_bin ALIAS network_bin) +target_link_libraries(network_bin PRIVATE + absl::str_format + sandbox2::client + sandbox2::comms + sandbox2::fileops + sapi::base +) diff --git a/sandboxed_api/sandbox2/examples/network/network_bin.cc b/sandboxed_api/sandbox2/examples/network/network_bin.cc new file mode 100644 index 0000000..444e883 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/network/network_bin.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. + +// This file is an example of a network sandboxed binary inside a network +// namespace. It can't connect with the server directly, but the executor can +// establish a connection and pass the connected socket to the sandboxee. + +#include +#include + +#include +#include + +#include "absl/strings/str_format.h" +#include "sandboxed_api/sandbox2/client.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/util.h" + +static ssize_t ReadFromFd(int fd, uint8_t* buf, size_t size) { + ssize_t received = 0; + while (received < size) { + ssize_t read_status = + TEMP_FAILURE_RETRY(read(fd, &buf[received], size - received)); + if (read_status == 0) { + break; + } + if (read_status < 0) { + return -1; + } + received += read_status; + } + return received; +} + +static bool CommunicationTest(int sock) { + char received[1025] = {0}; + + if (ReadFromFd(sock, reinterpret_cast(received), + sizeof(received) - 1) <= 0) { + LOG(ERROR) << "Data receiving error"; + return false; + } + absl::PrintF("Sandboxee received data from the server:\n\n%s\n", received); + if (strcmp(received, "Hello World\n")) { + LOG(ERROR) << "Data receiving error"; + return false; + } + + return true; +} + +int main(int argc, char** argv) { + // 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(); + + int client; + if (!comms.RecvFD(&client)) { + fputs("sandboxee: !comms.RecvFD(&client) failed\n", stderr); + return 1; + } + + if (!CommunicationTest(client)) return 2; + return 0; +} diff --git a/sandboxed_api/sandbox2/examples/network/network_sandbox.cc b/sandboxed_api/sandbox2/examples/network/network_sandbox.cc new file mode 100644 index 0000000..a6c4460 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/network/network_sandbox.cc @@ -0,0 +1,237 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 network binary. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "sandboxed_api/util/flag.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/sandbox2.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/runfiles.h" + +namespace { + +std::unique_ptr GetPolicy(absl::string_view sandboxee_path) { + return sandbox2::PolicyBuilder() + .EnableNamespaces() + .AllowExit() + .AllowMmap() + .AllowRead() + .AllowWrite() + .AllowSyscall(__NR_close) + .AllowSyscall(__NR_recvmsg) // RecvFD + .AllowSyscall(__NR_sendto) // send + .AllowStat() // printf,puts + .AddLibrariesForBinary(sandboxee_path) + .BuildOrDie(); +} + +void Server(int port) { + sandbox2::file_util::fileops::FDCloser s{ + socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0)}; + if (s.get() < 0) { + PLOG(ERROR) << "socket() failed"; + return; + } + + { + int enable = 1; + if (setsockopt(s.get(), SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < + 0) { + PLOG(ERROR) << "setsockopt(SO_REUSEADDR) failed"; + return; + } + } + + // Listen to localhost only. + struct sockaddr_in6 addr = {}; + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(port); + + int err = inet_pton(AF_INET6, "::1", &addr.sin6_addr.s6_addr); + if (err == 0) { + LOG(ERROR) << "inet_pton() failed"; + return; + } else if (err == -1) { + PLOG(ERROR) << "inet_pton() failed"; + return; + } + + if (bind(s.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) { + PLOG(ERROR) << "bind() failed"; + return; + } + + if (listen(s.get(), 1) < 0) { + PLOG(ERROR) << "listen() failed"; + return; + } + + sandbox2::file_util::fileops::FDCloser client{accept(s.get(), 0, 0)}; + if (client.get() < 0) { + PLOG(ERROR) << "accept() failed"; + return; + } + + const char msg[] = "Hello World\n"; + write(client.get(), msg, strlen(msg)); +} + +int ConnectToServer(int port) { + int s = socket(AF_INET6, SOCK_STREAM, 0); + if (s < 0) { + PLOG(ERROR) << "socket() failed"; + return -1; + } + + struct sockaddr_in6 saddr {}; + saddr.sin6_family = AF_INET6; + saddr.sin6_port = htons(port); + + int err = inet_pton(AF_INET6, "::1", &saddr.sin6_addr); + if (err == 0) { + LOG(ERROR) << "inet_pton() failed"; + close(s); + return -1; + } else if (err == -1) { + PLOG(ERROR) << "inet_pton() failed"; + close(s); + return -1; + } + + err = connect(s, reinterpret_cast(&saddr), + sizeof(saddr)); + if (err != 0) { + LOG(ERROR) << "connect() failed"; + close(s); + return -1; + } + + LOG(INFO) << "Connected to the server"; + return s; +} + +bool HandleSandboxee(sandbox2::Comms* comms, int port) { + // connect to the server and pass the file descriptor to sandboxee. + int client = ConnectToServer(port); + if (client <= 0) { + LOG(ERROR) << "connect_to_server() failed"; + return false; + } + + if (!comms->SendFD(client)) { + LOG(ERROR) << "sandboxee_comms->SendFD(client) failed"; + close(client); + return false; + } + close(client); + return true; +} + +} // namespace + +int main(int argc, char** argv) { + // This test is incompatible with sanitizers. + // The `SKIP_SANITIZERS_AND_COVERAGE` macro won't work for us here since we + // need to return something. +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) + return EXIT_SUCCESS; +#endif + + gflags::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); + int port = 8085; + std::thread server_thread{Server,port}; + server_thread.detach(); + + std::string path = sandbox2::GetInternalDataDependencyFilePath( + "sandbox2/examples/network/network_bin"); + std::vector args = {path}; + 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) + // Set cwd to / to get rids of warnings connected with file namespace. + .set_cwd("/"); + + 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 + // these many bytes to the file-system. + .set_rlimit_fsize(10000) + .set_rlimit_cpu(100) // The CPU time limit in seconds + .set_walltime_limit(absl::Seconds(100)); + + auto policy = GetPolicy(path); + sandbox2::Sandbox2 s2(std::move(executor), std::move(policy)); + auto* comms = s2.comms(); + + // Let the sandboxee run. + if (!s2.RunAsync()) { + auto result = s2.AwaitResult(); + LOG(ERROR) << "RunAsync failed: " << result.ToString(); + return 2; + } + + if (!HandleSandboxee(comms, port)) { + 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(); + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel new file mode 100644 index 0000000..c931d7a --- /dev/null +++ b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel @@ -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. + +# The 'network proxy' example demonstrates how to use network proxy server. + +licenses(["notice"]) # Apache 2.0 + +load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts") + +# Executor +cc_binary( + name = "networkproxy_sandbox", + srcs = ["networkproxy_sandbox.cc"], + copts = sapi_platform_copts(), + data = [":networkproxy_bin"], + deps = [ + "//sandboxed_api/sandbox2", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2/util:bpf_helper", + "//sandboxed_api/sandbox2/util:fileops", + "//sandboxed_api/sandbox2/util:runfiles", + "//sandboxed_api/util:flags", + ], +) + +# Sandboxee +cc_binary( + name = "networkproxy_bin", + srcs = ["networkproxy_bin.cc"], + copts = sapi_platform_copts(), + deps = [ + "//sandboxed_api/sandbox2:client", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2:network_proxy_client", + "//sandboxed_api/sandbox2/util:fileops", + "@com_google_absl//absl/strings:str_format", + ], +) diff --git a/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt b/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt new file mode 100644 index 0000000..efae2dd --- /dev/null +++ b/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright 2019 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# sandboxed_api/sandbox2/examples/network_proxy:networkproxy_sandbox +add_executable(sandbox2_networkproxy_sandbox + networkproxy_sandbox.cc +) +add_executable(sandbox2::networkproxy_sandbox ALIAS sandbox2_networkproxy_sandbox) +add_dependencies(sandbox2_networkproxy_sandbox + sandbox2::networkproxy_bin +) +target_link_libraries(sandbox2_networkproxy_sandbox PRIVATE + glog::glog + sandbox2::bpf_helper + sandbox2::comms + sandbox2::fileops + sandbox2::runfiles + sandbox2::sandbox2 + sapi::base +) + +# sandboxed_api/sandbox2/examples/networkproxy_bin:networkproxy_bin +add_executable(networkproxy_bin + networkproxy_bin.cc +) +add_executable(sandbox2::networkproxy_bin ALIAS networkproxy_bin) +target_link_libraries(networkproxy_bin PRIVATE + absl::str_format + sandbox2::client + sandbox2::comms + sandbox2::fileops + sandbox2::network_proxy_client + sapi::base +) diff --git a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc new file mode 100644 index 0000000..cd57021 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc @@ -0,0 +1,117 @@ +// This file is an example of a network sandboxed binary inside a network +// namespace. It can't connect with the server directly, but the executor can +// establish a connection and pass the connected socket to the sandboxee. + +#include +#include +#include +#include +#include +#include + +#include + +#include "absl/strings/str_format.h" +#include "sandboxed_api/sandbox2/client.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/network_proxy_client.h" +#include "sandboxed_api/sandbox2/util/fileops.h" + +static ssize_t ReadFromFd(int fd, uint8_t* buf, size_t size) { + ssize_t received = 0; + while (received < size) { + ssize_t read_status = + TEMP_FAILURE_RETRY(read(fd, &buf[received], size - received)); + if (read_status == 0) { + break; + } + if (read_status < 0) { + return -1; + } + received += read_status; + } + return received; +} + +static bool CommunicationTest(int sock) { + char received[1025] = {0}; + + if (ReadFromFd(sock, reinterpret_cast(received), + sizeof(received) - 1) <= 0) { + LOG(ERROR) << "Data receiving error"; + return false; + } + absl::PrintF("Sandboxee received data from the server:\n\n%s\n", received); + if (strcmp(received, "Hello World\n")) { + LOG(ERROR) << "Data receiving error"; + return false; + } + + return true; +} + +static int ConnectToServer(int port) { + int s = socket(AF_INET6, SOCK_STREAM, 0); + if (s < 0) { + PLOG(ERROR) << "socket() failed"; + return -1; + } + + struct sockaddr_in6 saddr {}; + saddr.sin6_family = AF_INET6; + saddr.sin6_port = htons(port); + + int err = inet_pton(AF_INET6, "::1", &saddr.sin6_addr); + if (err == 0) { + LOG(ERROR) << "inet_pton() failed"; + close(s); + return -1; + } else if (err == -1) { + PLOG(ERROR) << "inet_pton() failed"; + close(s); + return -1; + } + + err = connect(s, reinterpret_cast(&saddr), + sizeof(saddr)); + if (err != 0) { + LOG(ERROR) << "connect() failed"; + close(s); + return -1; + } + + LOG(INFO) << "Connected to the server"; + return s; +} + +int main(int argc, char** argv) { + // 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(); + + sapi::Status status = sandbox2_client.InstallNetworkProxyHandler(); + if (!status.ok()) { + LOG(ERROR) << "InstallNetworkProxyHandler() failed: " << status.message(); + return 1; + } + + // Receive port number of the server + int port; + if (!comms.RecvInt32(&port)) { + LOG(ERROR) << "sandboxee_comms->RecvUint32(&crc4) failed"; + return 2; + } + + sandbox2::file_util::fileops::FDCloser client{ConnectToServer(port)}; + if (client.get() == -1) { + return 3; + } + + if (!CommunicationTest(client.get())) { + return 4; + } + return 0; +} diff --git a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc new file mode 100644 index 0000000..605f9b1 --- /dev/null +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc @@ -0,0 +1,176 @@ +// A demo sandbox for the network binary. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "sandboxed_api/util/flag.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/sandbox2.h" +#include "sandboxed_api/sandbox2/util/bpf_helper.h" +#include "sandboxed_api/sandbox2/util/fileops.h" +#include "sandboxed_api/sandbox2/util/runfiles.h" + +namespace { + +std::unique_ptr GetPolicy(absl::string_view sandboxee_path) { + return sandbox2::PolicyBuilder() + .AllowExit() + .EnableNamespaces() + .AllowMmap() + .AllowRead() + .AllowWrite() + .AllowStat() // printf, puts + .AllowOpen() + .AllowSyscall(__NR_sendto) // send + .AllowSyscall(__NR_lseek) + .AllowSyscall(__NR_munmap) + .AllowSyscall(__NR_getpid) + .AddNetworkProxyHandlerPolicy() + .AddLibrariesForBinary(sandboxee_path) + .BuildOrDie(); +} + +void Server(int port) { + sandbox2::file_util::fileops::FDCloser s{ + socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0)}; + if (s.get() < 0) { + PLOG(ERROR) << "socket() failed"; + return; + } + + { + int enable = 1; + if (setsockopt(s.get(), SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < + 0) { + PLOG(ERROR) << "setsockopt(SO_REUSEADDR) failed"; + return; + } + } + + // Listen to localhost only. + struct sockaddr_in6 addr = {}; + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(port); + + int err = inet_pton(AF_INET6, "::1", &addr.sin6_addr.s6_addr); + if (err == 0) { + LOG(ERROR) << "inet_pton() failed"; + return; + } else if (err == -1) { + PLOG(ERROR) << "inet_pton() failed"; + return; + } + + if (bind(s.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) { + PLOG(ERROR) << "bind() failed"; + return; + } + + if (listen(s.get(), 1) < 0) { + PLOG(ERROR) << "listen() failed"; + return; + } + + sandbox2::file_util::fileops::FDCloser client{accept(s.get(), 0, 0)}; + if (client.get() < 0) { + PLOG(ERROR) << "accept() failed"; + return; + } + + const char kMsg[] = "Hello World\n"; + write(client.get(), kMsg, ABSL_ARRAYSIZE(kMsg)); +} + +} // namespace + +int main(int argc, char** argv) { + // This test is incompatible with sanitizers. + // The `SKIP_SANITIZERS_AND_COVERAGE` macro won't work for us here since we + // need to return something. +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) + return EXIT_SUCCESS; +#endif + + gflags::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); + int port = 8085; + std::thread server_thread{Server,port}; + server_thread.detach(); + + std::string path = sandbox2::GetInternalDataDependencyFilePath( + "sandbox2/examples/network_proxy/networkproxy_bin"); + std::vector args = {path}; + std::vector envs = {}; + + auto executor = absl::make_unique(path, args, envs); + + executor + // Sandboxing is enabled by the binary itself (i.e. the networkproxy_bin + // is capable of enabling sandboxing on its own). + ->set_enable_sandbox_before_exec(false) + // Set cwd to / to get rids of warnings connected with file namespace. + .set_cwd("/") + // Enable built-in network proxy. + .ipc() + ->EnableNetworkProxyServer(); + 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 + // these many bytes to the file-system. + .set_rlimit_fsize(10000) + // The CPU time limit in seconds. + .set_rlimit_cpu(100) + .set_walltime_limit(absl::Seconds(100)); + + auto policy = GetPolicy(path); + sandbox2::Sandbox2 s2(std::move(executor), std::move(policy)); + auto* comms = s2.comms(); + + // Let the sandboxee run. + if (!s2.RunAsync()) { + auto result = s2.AwaitResult(); + LOG(ERROR) << "RunAsync failed: " << result.ToString(); + return 2; + } + + // Send the port number via comms + if (!comms->SendInt32(port)) { + LOG(ERROR) << "sandboxee_comms->SendInt32() failed"; + return 3; + } + + auto result = s2.AwaitResult(); + if (result.final_status() != sandbox2::Result::OK) { + LOG(ERROR) << "Sandbox error: " << result.ToString(); + return 4; // e.g. sandbox violation, signal (sigsegv) + } + auto code = result.reason_code(); + if (code) { + LOG(ERROR) << "Sandboxee exited with non-zero: " << code; + return 5; // e.g. normal child error + } + + LOG(INFO) << "Sandboxee finished: " << result.ToString(); + return EXIT_SUCCESS; +} diff --git a/sandboxed_api/sandbox2/network_proxy_client.cc b/sandboxed_api/sandbox2/network_proxy_client.cc index be7dc45..ed1e164 100644 --- a/sandboxed_api/sandbox2/network_proxy_client.cc +++ b/sandboxed_api/sandbox2/network_proxy_client.cc @@ -14,10 +14,17 @@ #include "sandboxed_api/sandbox2/network_proxy_client.h" +#include +#include +#include +#include +#include + #include #include #include +#include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/util/canonical_errors.h" @@ -26,6 +33,25 @@ namespace sandbox2 { +#ifndef SYS_SECCOMP +constexpr int SYS_SECCOMP = 1; +#endif + +#if defined(__x86_64__) +constexpr int kRegResult = REG_RAX; +constexpr int kRegSyscall = REG_RAX; +constexpr int kRegArg0 = REG_RDI; +constexpr int kRegArg1 = REG_RSI; +constexpr int kRegArg2 = REG_RDX; +#endif +#if defined(__powerpc64__) +constexpr int kRegResult = 3; +constexpr int kRegSyscall = 0; +constexpr int kRegArg0 = 3; +constexpr int kRegArg1 = 4; +constexpr int kRegArg2 = 5; +#endif + constexpr char NetworkProxyClient::kFDName[]; int NetworkProxyClient::ConnectHandler(int sockfd, const struct sockaddr* addr, @@ -34,7 +60,7 @@ int NetworkProxyClient::ConnectHandler(int sockfd, const struct sockaddr* addr, if (status.ok()) { return 0; } - PLOG(ERROR) << "ConnectHandler() failed: " << status.message(); + LOG(ERROR) << "ConnectHandler() failed: " << status.message(); return -1; } @@ -86,9 +112,106 @@ sapi::Status NetworkProxyClient::ReceiveRemoteResult() { if (result != 0) { errno = result; return sapi::InternalError( - absl::StrCat("Error in network proxy: ", StrError(errno))); + absl::StrCat("Error in network proxy server: ", StrError(errno))); } return sapi::OkStatus(); } +namespace { + +static NetworkProxyHandler* g_network_proxy_handler = nullptr; + +void SignalHandler(int nr, siginfo_t* info, void* void_context) { + g_network_proxy_handler->ProcessSeccompTrap(nr, info, void_context); +} + +} // namespace + +sapi::Status NetworkProxyHandler::InstallNetworkProxyHandler( + NetworkProxyClient* npc) { + if (g_network_proxy_handler) { + return sapi::AlreadyExistsError( + "Network proxy handler is already installed"); + } + g_network_proxy_handler = new NetworkProxyHandler(npc); + return sapi::OkStatus(); +} + +void NetworkProxyHandler::InvokeOldAct(int nr, siginfo_t* info, + void* void_context) { + if (oldact_.sa_flags & SA_SIGINFO) { + if (oldact_.sa_sigaction) { + oldact_.sa_sigaction(nr, info, void_context); + } + } else if (oldact_.sa_handler == SIG_IGN) { + return; + } else if (oldact_.sa_handler == SIG_DFL) { + sigaction(SIGSYS, &oldact_, nullptr); + raise(SIGSYS); + } else if (oldact_.sa_handler) { + oldact_.sa_handler(nr); + } +} // namespace sandbox2 + +void NetworkProxyHandler::ProcessSeccompTrap(int nr, siginfo_t* info, + void* void_context) { + ucontext_t* ctx = (ucontext_t*)(void_context); + if (info->si_code != SYS_SECCOMP) { + InvokeOldAct(nr, info, void_context); + return; + } + if (!ctx) return; + +#if defined(__x86_64__) + auto* registers = ctx->uc_mcontext.gregs; +#elif defined(__powerpc64__) + auto* registers = ctx->uc_mcontext.gp_regs; + using ppc_gpreg_t = std::decay::type; +#endif + + int syscall = registers[kRegSyscall]; + + int sockfd; + const struct sockaddr* addr; + socklen_t addrlen; + + if (syscall == __NR_connect) { + sockfd = static_cast(registers[kRegArg0]); + addr = reinterpret_cast(registers[kRegArg1]); + addrlen = static_cast(registers[kRegArg2]); +#if defined(__powerpc64__) + } else if (syscall == __NR_socketcall && + static_cast(registers[kRegArg0]) == SYS_CONNECT) { + ppc_gpreg_t* args = reinterpret_cast(registers[kRegArg1]); + + sockfd = static_cast(args[0]); + addr = reinterpret_cast(args[1]); + addrlen = static_cast(args[2]); +#endif + } else { + InvokeOldAct(nr, info, void_context); + return; + } + + sapi::Status result = network_proxy_client_->Connect(sockfd, addr, addrlen); + if (result.ok()) { + registers[kRegResult] = 0; + } else { + registers[kRegResult] = -errno; + } +} + +void NetworkProxyHandler::InstallSeccompTrap() { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGSYS); + + struct sigaction act = {}; + act.sa_sigaction = &SignalHandler; + act.sa_flags = SA_SIGINFO; + + CHECK_EQ(sigaction(SIGSYS, &act, &oldact_), 0); + CHECK_EQ(sigprocmask(SIG_UNBLOCK, &mask, nullptr), 0); +} + } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy_client.h b/sandboxed_api/sandbox2/network_proxy_client.h index a013c53..1491c47 100644 --- a/sandboxed_api/sandbox2/network_proxy_client.h +++ b/sandboxed_api/sandbox2/network_proxy_client.h @@ -50,6 +50,26 @@ class NetworkProxyClient { absl::Mutex mutex_; }; +class NetworkProxyHandler { + public: + // Installs the handler that redirects connect() syscalls to the trap + // function. This function exchange data with NetworkProxyServer that checks + // if this connection is allowed and sends the connected socket to us. + // In other words, this function just use NetworkProxyClient class. + static sapi::Status InstallNetworkProxyHandler(NetworkProxyClient* npc); + void ProcessSeccompTrap(int nr, siginfo_t* info, void* void_context); + + private: + NetworkProxyHandler(NetworkProxyClient* npc) : network_proxy_client_(npc) { + InstallSeccompTrap(); + } + void InvokeOldAct(int nr, siginfo_t* info, void* void_context); + void InstallSeccompTrap(); + + struct sigaction oldact_; + NetworkProxyClient* network_proxy_client_; +}; + } // 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 index 8394971..8d1d26e 100644 --- a/sandboxed_api/sandbox2/network_proxy_server.cc +++ b/sandboxed_api/sandbox2/network_proxy_server.cc @@ -39,32 +39,35 @@ void NetworkProxyServer::ProcessConnectRequest() { 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); + if (!((addr.size() == sizeof(sockaddr_in) && saddr->sin_family == AF_INET) || + (addr.size() == sizeof(sockaddr_in6) && + saddr->sin_family == AF_INET6))) { + SendError(EINVAL); return; } int new_socket = socket(saddr->sin_family, SOCK_STREAM, 0); + if (new_socket < 0) { + SendError(errno); + return; + } + 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; + if (result == 0) { + NotifySuccess(); + if (!fatal_error_) { + if (!comms_->SendFD(new_socket)) { + fatal_error_ = true; + return; + } } } } @@ -77,8 +80,14 @@ void NetworkProxyServer::Run() { << "Clean shutdown or error occurred, shutting down NetworkProxyServer"; } -void NetworkProxyServer::SendResult(int result, int saved_errno) { - if (!comms_->SendInt32(result == 0 ? result : saved_errno)) { +void NetworkProxyServer::SendError(int saved_errno) { + if (!comms_->SendInt32(saved_errno)) { + fatal_error_ = true; + } +} + +void NetworkProxyServer::NotifySuccess() { + if (!comms_->SendInt32(0)) { fatal_error_ = true; } } diff --git a/sandboxed_api/sandbox2/network_proxy_server.h b/sandboxed_api/sandbox2/network_proxy_server.h index 91c6b24..a239e29 100644 --- a/sandboxed_api/sandbox2/network_proxy_server.h +++ b/sandboxed_api/sandbox2/network_proxy_server.h @@ -35,11 +35,13 @@ class NetworkProxyServer { 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); + // Notifies the network proxy client about the error and sends its code. + void SendError(int saved_errno); + // Notifies the network proxy client that no error occurred. + void NotifySuccess(); + + // Serves connection requests from the network proxy client. void ProcessConnectRequest(); std::unique_ptr comms_; diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc index e70636e..231e691 100644 --- a/sandboxed_api/sandbox2/policybuilder.cc +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -23,14 +23,17 @@ #endif #include // For the fcntl flags #include +#include // For SYS_CONNECT #include // For GRND_NONBLOCK #include // For mmap arguments +#include #include #include #include #include +#include "absl/memory/memory.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" #include "sandboxed_api/sandbox2/namespace.h" @@ -557,31 +560,33 @@ PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall(unsigned int num, BpfFunc 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; - }); + 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()); @@ -840,6 +845,66 @@ PolicyBuilder& PolicyBuilder::CollectStacktracesOnKill(bool enable) { return *this; } +PolicyBuilder& PolicyBuilder::AddNetworkProxyPolicy() { + AllowFutexOp(FUTEX_WAKE); + AllowFutexOp(FUTEX_WAIT); + AllowFutexOp(FUTEX_WAIT_BITSET); + AllowSyscalls({ + __NR_dup2, + __NR_recvmsg, + __NR_close, + __NR_gettid, + }); + AddPolicyOnSyscall(__NR_socket, { + ARG_32(0), + JEQ32(AF_INET, ALLOW), + JEQ32(AF_INET6, ALLOW), + }); + AddPolicyOnSyscall(__NR_getsockopt, + [](bpf_labels& labels) -> std::vector { + return { + ARG_32(1), + JNE32(SOL_SOCKET, JUMP(&labels, getsockopt_end)), + ARG_32(2), + JEQ32(SO_TYPE, ALLOW), + LABEL(&labels, getsockopt_end), + }; + }); +#if defined(__powerpc64__) + AddPolicyOnSyscall(__NR_socketcall, { + ARG_32(0), + JEQ32(SYS_SOCKET, ALLOW), + JEQ32(SYS_GETSOCKOPT, ALLOW), + JEQ32(SYS_RECVMSG, ALLOW), + }); +#endif + return *this; +} + +PolicyBuilder& PolicyBuilder::AddNetworkProxyHandlerPolicy() { + AddNetworkProxyPolicy(); + AllowSyscall(__NR_rt_sigreturn); + + AddPolicyOnSyscall(__NR_rt_sigaction, { + ARG_32(0), + JEQ32(SIGSYS, ALLOW), + }); + + AddPolicyOnSyscall(__NR_rt_sigprocmask, { + ARG_32(0), + JEQ32(SIG_UNBLOCK, ALLOW), + }); + + AddPolicyOnSyscall(__NR_connect, {TRAP(0)}); +#if defined(__powerpc64__) + AddPolicyOnSyscall(__NR_socketcall, { + ARG_32(0), + JEQ32(SYS_CONNECT, TRAP(0)), + }); +#endif + return *this; +} + void PolicyBuilder::StoreDescription(PolicyBuilderDescription* pb_description) { for (const auto& handled_syscall : handled_syscalls_) { pb_description->add_handled_syscalls(handled_syscall); diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h index 1fc46d2..6d77a0f 100644 --- a/sandboxed_api/sandbox2/policybuilder.h +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -497,6 +497,13 @@ class PolicyBuilder final { // sandbox-team@ first if unsure. PolicyBuilder& DangerDefaultAllowAll(); + // Allows syscalls that are necessary for the NetworkProxyClient + PolicyBuilder& AddNetworkProxyPolicy(); + + // Allows syscalls that are necessary for the NetworkProxyClient and + // the NetworkProxyHandler + PolicyBuilder& AddNetworkProxyHandlerPolicy(); + private: friend class PolicyBuilderPeer; // For testing friend class StackTracePeer;