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;