diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index 70532cc..6cdeb16 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -1094,3 +1094,21 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "network_proxy_test", + srcs = ["network_proxy_test.cc"], + copts = sapi_platform_copts(), + data = [ + "//sandboxed_api/sandbox2/testcases:network_proxy", + ], + tags = ["no_qemu_user_mode"], + deps = [ + ":sandbox2", + "//sandboxed_api:testing", + "//sandboxed_api/sandbox2/network_proxy:testing", + "//sandboxed_api/util:status_matchers", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sandboxed_api/sandbox2/CMakeLists.txt b/sandboxed_api/sandbox2/CMakeLists.txt index feed59e..10218e0 100644 --- a/sandboxed_api/sandbox2/CMakeLists.txt +++ b/sandboxed_api/sandbox2/CMakeLists.txt @@ -1197,6 +1197,27 @@ if(BUILD_TESTING AND SAPI_BUILD_TESTING) ENVIRONMENT "TEST_TMPDIR=/tmp" ENVIRONMENT "TEST_SRCDIR=${PROJECT_BINARY_DIR}" ) + + # sandboxed_api/sandbox2:network_proxy_test + add_executable(sandbox2_network_proxy_test + network_proxy_test.cc + ) + set_target_properties(sandbox2_network_proxy_test PROPERTIES + OUTPUT_NAME network_proxy_test + ) + target_link_libraries(sandbox2_network_proxy_test + PRIVATE absl::strings + sandbox2::sandbox2 + sandbox2::network_proxy_testing + sapi::status_matchers + sapi::testing + sapi::test_main + ) + gtest_discover_tests_xcompile(sandbox2_bpfdisassembler_test PROPERTIES + ENVIRONMENT "TEST_TMPDIR=/tmp" + ENVIRONMENT "TEST_SRCDIR=${PROJECT_BINARY_DIR}" + ) + endif() configure_file( diff --git a/sandboxed_api/sandbox2/examples/network/BUILD.bazel b/sandboxed_api/sandbox2/examples/network/BUILD.bazel index 82ccef6..11baced 100644 --- a/sandboxed_api/sandbox2/examples/network/BUILD.bazel +++ b/sandboxed_api/sandbox2/examples/network/BUILD.bazel @@ -36,14 +36,14 @@ cc_binary( "//sandboxed_api:config", "//sandboxed_api/sandbox2", "//sandboxed_api/sandbox2:comms", - "//sandboxed_api/sandbox2/util:bpf_helper", - "//sandboxed_api/util:fileops", + "//sandboxed_api/sandbox2/network_proxy:testing", "//sandboxed_api/util:runfiles", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/flags:parse", "@com_google_absl//absl/log", "@com_google_absl//absl/log:globals", "@com_google_absl//absl/log:initialize", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/time", ], diff --git a/sandboxed_api/sandbox2/examples/network/CMakeLists.txt b/sandboxed_api/sandbox2/examples/network/CMakeLists.txt index acbef87..f443b98 100644 --- a/sandboxed_api/sandbox2/examples/network/CMakeLists.txt +++ b/sandboxed_api/sandbox2/examples/network/CMakeLists.txt @@ -27,11 +27,12 @@ target_link_libraries(sandbox2_network_sandbox PRIVATE absl::log_globals absl::log_initialize absl::log_severity + absl::statusor absl::strings absl::time sandbox2::bpf_helper sandbox2::comms - sapi::fileops + sandbox2::network_proxy_testing sapi::runfiles sandbox2::sandbox2 sapi::base diff --git a/sandboxed_api/sandbox2/examples/network/network_sandbox.cc b/sandboxed_api/sandbox2/examples/network/network_sandbox.cc index 896faef..8ad392c 100644 --- a/sandboxed_api/sandbox2/examples/network/network_sandbox.cc +++ b/sandboxed_api/sandbox2/examples/network/network_sandbox.cc @@ -26,22 +26,22 @@ #include #include -#include "absl/base/macros.h" #include "absl/flags/parse.h" #include "absl/log/globals.h" #include "absl/log/initialize.h" #include "absl/log/log.h" #include "absl/base/log_severity.h" +#include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "sandboxed_api/config.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/network_proxy/testing.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/util/fileops.h" #include "sandboxed_api/util/runfiles.h" namespace { @@ -60,58 +60,6 @@ std::unique_ptr GetPolicy(absl::string_view sandboxee_path) { .BuildOrDie(); } -void Server(int port) { - sapi::file_util::fileops::FDCloser s( - socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0)); - if (s.get() < 0) { - PLOG(ERROR) << "socket() failed"; - return; - } - - if (int enable = 1; 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; - } - if (err == -1) { - PLOG(ERROR) << "inet_pton() failed"; - return; - } - - if (bind(s.get(), reinterpret_cast(&addr), sizeof(addr)) < - 0) { - PLOG(ERROR) << "bind() failed"; - return; - } - - if (listen(s.get(), 1) < 0) { - PLOG(ERROR) << "listen() failed"; - return; - } - - sapi::file_util::fileops::FDCloser client(accept(s.get(), 0, 0)); - if (client.get() < 0) { - PLOG(ERROR) << "accept() failed"; - return; - } - - constexpr char kMsg[] = "Hello World\n"; - if (write(client.get(), kMsg, ABSL_ARRAYSIZE(kMsg) - 1) < 0) { - PLOG(ERROR) << "write() failed"; - } -} - int ConnectToServer(int port) { int s = socket(AF_INET6, SOCK_STREAM, 0); if (s < 0) { @@ -177,9 +125,12 @@ int main(int argc, char* argv[]) { absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); absl::ParseCommandLine(argc, argv); absl::InitializeLog(); - int port = 8085; - std::thread server_thread{Server,port}; - server_thread.detach(); + + absl::StatusOr port = sandbox2::StartNetworkProxyTestServer(); + if (!port.ok()) { + LOG(ERROR) << port.status(); + return EXIT_FAILURE; + } // Note: In your own code, use sapi::GetDataDependencyFilePath() instead. const std::string path = sapi::internal::GetSapiDataDependencyFilePath( @@ -214,7 +165,7 @@ int main(int argc, char* argv[]) { return 2; } - if (!HandleSandboxee(comms, port)) { + 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 diff --git a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel index 03e2e36..7f94437 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel @@ -32,7 +32,7 @@ cc_binary( "//sandboxed_api:config", "//sandboxed_api/sandbox2", "//sandboxed_api/sandbox2:comms", - "//sandboxed_api/util:fileops", + "//sandboxed_api/sandbox2/network_proxy:testing", "//sandboxed_api/util:runfiles", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/flags:flag", @@ -40,6 +40,7 @@ cc_binary( "@com_google_absl//absl/log", "@com_google_absl//absl/log:globals", "@com_google_absl//absl/log:initialize", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/time", ], diff --git a/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt b/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt index 73889ff..9f7f649 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt +++ b/sandboxed_api/sandbox2/examples/network_proxy/CMakeLists.txt @@ -27,11 +27,12 @@ target_link_libraries(sandbox2_networkproxy_sandbox PRIVATE absl::log_globals absl::log_initialize absl::log_severity + absl::statusor absl::strings absl::time sandbox2::bpf_helper sandbox2::comms - sapi::fileops + sandbox2::network_proxy_testing sapi::runfiles sandbox2::sandbox2 sapi::base diff --git a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc index 4cd9fb5..537a904 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc @@ -12,22 +12,23 @@ #include #include -#include "absl/base/macros.h" #include "absl/flags/flag.h" #include "absl/flags/parse.h" #include "absl/log/globals.h" #include "absl/log/initialize.h" #include "absl/log/log.h" #include "absl/base/log_severity.h" +#include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "sandboxed_api/config.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/network_proxy/testing.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/util/fileops.h" #include "sandboxed_api/util/runfiles.h" ABSL_FLAG(bool, connect_with_handler, true, "Connect using automatic mode."); @@ -59,59 +60,6 @@ std::unique_ptr GetPolicy(absl::string_view sandboxee_path) { return builder.AllowIPv6("::1").BuildOrDie(); } -void Server(int port) { - sapi::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; - } - - sapi::file_util::fileops::FDCloser client{accept(s.get(), 0, 0)}; - if (client.get() < 0) { - PLOG(ERROR) << "accept() failed"; - return; - } - - constexpr char kMsg[] = "Hello World\n"; - if (write(client.get(), kMsg, ABSL_ARRAYSIZE(kMsg) - 1) < 0) { - PLOG(ERROR) << "write() failed"; - } -} - } // namespace int main(int argc, char* argv[]) { @@ -125,9 +73,12 @@ int main(int argc, char* argv[]) { absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); absl::ParseCommandLine(argc, argv); absl::InitializeLog(); - int port = 8085; - std::thread server_thread{Server, port}; - server_thread.detach(); + + absl::StatusOr port = sandbox2::StartNetworkProxyTestServer(); + if (!port.ok()) { + LOG(ERROR) << port.status(); + return EXIT_FAILURE; + } // Note: In your own code, use sapi::GetDataDependencyFilePath() instead. const std::string path = @@ -157,7 +108,7 @@ int main(int argc, char* argv[]) { auto policy = GetPolicy(path); sandbox2::Sandbox2 s2(std::move(executor), std::move(policy)); - auto* comms = s2.comms(); + sandbox2::Comms* comms = s2.comms(); // Let the sandboxee run. if (!s2.RunAsync()) { @@ -167,7 +118,7 @@ int main(int argc, char* argv[]) { } // Send the port number via comms - if (!comms->SendInt32(port)) { + if (!comms->SendInt32(*port)) { LOG(ERROR) << "sandboxee_comms->SendInt32() failed"; return 3; } diff --git a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel index 296910e..40df1fd 100644 --- a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel @@ -77,3 +77,16 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_library( + name = "testing", + srcs = ["testing.cc"], + hdrs = ["testing.h"], + deps = [ + "//sandboxed_api/util:fileops", + "@com_google_absl//absl/log", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + ], +) diff --git a/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt b/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt index f74dd03..16ed82e 100644 --- a/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt +++ b/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt @@ -60,6 +60,21 @@ target_link_libraries(sandbox2_network_proxy_client PRIVATE sapi::status ) +# sandboxed_api/sandbox2/network_proxy:testing +add_library(sandbox2_network_proxy_testing ${SAPI_LIB_TYPE} + testing.cc + testing.h +) +add_library(sandbox2::network_proxy_testing ALIAS sandbox2_network_proxy_testing) +target_link_libraries(sandbox2_network_proxy_testing PRIVATE + absl::log + absl::status + absl::statusor + absl::strings + sapi::fileops + sapi::base +) + if(BUILD_TESTING AND SAPI_BUILD_TESTING) # sandboxed_api/sandbox2/network_proxy:filtering_test add_executable(sandbox2_filtering_test diff --git a/sandboxed_api/sandbox2/network_proxy/testing.cc b/sandboxed_api/sandbox2/network_proxy/testing.cc new file mode 100644 index 0000000..7af2fa7 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy/testing.cc @@ -0,0 +1,91 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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/testing.h" + +#include +#include +#include + +#include + +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/util/fileops.h" + +namespace sandbox2 { +namespace { +void ServerThread(int port) { + sapi::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; + } + + sapi::file_util::fileops::FDCloser client{accept(s.get(), 0, 0)}; + if (client.get() < 0) { + PLOG(ERROR) << "accept() failed"; + return; + } + + constexpr absl::string_view kMsg = "Hello World\n"; + if (write(client.get(), kMsg.data(), kMsg.size()) < 0) { + PLOG(ERROR) << "write() failed"; + } +} +} // namespace + +absl::StatusOr StartNetworkProxyTestServer() { + static int port = 8085; + std::thread server_thread([] { ServerThread(++port); }); + server_thread.detach(); + return port; +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy/testing.h b/sandboxed_api/sandbox2/network_proxy/testing.h new file mode 100644 index 0000000..5689f7e --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy/testing.h @@ -0,0 +1,27 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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_EXAMPLES_NETWORK_PROXY_NETWORKPROXY_LIB_H_ +#define SANDBOXED_API_SANDBOX2_EXAMPLES_NETWORK_PROXY_NETWORKPROXY_LIB_H_ + +#include "absl/status/statusor.h" + +namespace sandbox2 { + +// Returns the port the server was started on. +absl::StatusOr StartNetworkProxyTestServer(); + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_EXAMPLES_NETWORK_PROXY_NETWORKPROXY_LIB_H_ diff --git a/sandboxed_api/sandbox2/network_proxy_test.cc b/sandboxed_api/sandbox2/network_proxy_test.cc new file mode 100644 index 0000000..0055026 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy_test.cc @@ -0,0 +1,135 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/network_proxy/testing.h" +#include "sandboxed_api/sandbox2/policybuilder.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/sandbox2.h" +#include "sandboxed_api/testing.h" +#include "sandboxed_api/util/status_matchers.h" + +namespace sandbox2 { +namespace { + +using ::sapi::GetTestSourcePath; +using ::testing::Eq; + +TEST(NetworkProxy, ProxyWithHandlerAllowed) { + SKIP_SANITIZERS; + const std::string path = + GetTestSourcePath("sandbox2/testcases/network_proxy"); + std::vector args = {"network_proxy"}; + auto executor = std::make_unique(path, args); + + PolicyBuilder builder; + builder.AllowDynamicStartup() + .AllowWrite() + .AllowRead() + .AllowExit() + .AllowSyscall(__NR_sendto) + .AllowTcMalloc() + .AddNetworkProxyHandlerPolicy() + .AllowLlvmCoverage() + .AllowIPv6("::1") + .AddLibrariesForBinary(path); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, builder.TryBuild()); + + Sandbox2 s2(std::move(executor), std::move(policy)); + ASSERT_TRUE(s2.RunAsync()); + + SAPI_ASSERT_OK_AND_ASSIGN(int port, StartNetworkProxyTestServer()); + ASSERT_TRUE(s2.comms()->SendInt32(port)); + + sandbox2::Result result = s2.AwaitResult(); + ASSERT_THAT(result.final_status(), Eq(Result::OK)); + EXPECT_THAT(result.reason_code(), Eq(EXIT_SUCCESS)); +} + +TEST(NetworkProxy, ProxyWithHandlerNotAllowed) { + SKIP_SANITIZERS; + const std::string path = + GetTestSourcePath("sandbox2/testcases/network_proxy"); + std::vector args = {"network_proxy"}; + auto executor = std::make_unique(path, args); + + PolicyBuilder builder; + builder.AllowDynamicStartup() + .AllowWrite() + .AllowRead() + .AllowExit() + .AllowSyscall(__NR_sendto) + .AllowTcMalloc() + .AddNetworkProxyHandlerPolicy() + .AllowLlvmCoverage() + .AddLibrariesForBinary(path); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, builder.TryBuild()); + + Sandbox2 s2(std::move(executor), std::move(policy)); + ASSERT_TRUE(s2.RunAsync()); + + SAPI_ASSERT_OK_AND_ASSIGN(int port, StartNetworkProxyTestServer()); + ASSERT_TRUE(s2.comms()->SendInt32(port)); + + sandbox2::Result result = s2.AwaitResult(); + ASSERT_THAT(result.final_status(), Eq(Result::VIOLATION)); + EXPECT_THAT(result.reason_code(), Eq(Result::VIOLATION_NETWORK)); +} + +TEST(NetworkProxy, ProxyWithoutHandlerAllowed) { + SKIP_SANITIZERS; + const std::string path = + GetTestSourcePath("sandbox2/testcases/network_proxy"); + std::vector args = {"network_proxy", "--noconnect_with_handler"}; + auto executor = std::make_unique(path, args); + + PolicyBuilder builder; + builder.AllowDynamicStartup() + .AllowExit() + .AllowWrite() + .AllowRead() + .AllowSyscall(__NR_sendto) + .AllowTcMalloc() + .AddNetworkProxyHandlerPolicy() + .AllowLlvmCoverage() + .AllowIPv6("::1") + .AddLibrariesForBinary(path); + + SAPI_ASSERT_OK_AND_ASSIGN(auto policy, builder.TryBuild()); + + Sandbox2 s2(std::move(executor), std::move(policy)); + ASSERT_TRUE(s2.RunAsync()); + + SAPI_ASSERT_OK_AND_ASSIGN(int port, StartNetworkProxyTestServer()); + ASSERT_TRUE(s2.comms()->SendInt32(port)); + + sandbox2::Result result = s2.AwaitResult(); + ASSERT_THAT(result.final_status(), Eq(Result::OK)); + EXPECT_THAT(result.reason_code(), Eq(EXIT_SUCCESS)); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/policy_test.cc b/sandboxed_api/sandbox2/policy_test.cc index 422d658..4b02137 100644 --- a/sandboxed_api/sandbox2/policy_test.cc +++ b/sandboxed_api/sandbox2/policy_test.cc @@ -137,7 +137,7 @@ TEST(PolicyTest, BpfPtracePermissionDenied) { TEST(PolicyTest, IsattyAllowed) { SKIP_SANITIZERS; - sandbox2::PolicyBuilder builder; + PolicyBuilder builder; if constexpr (sapi::host_os::IsAndroid()) { builder.DisableNamespaces().AllowDynamicStartup(); } @@ -157,7 +157,7 @@ TEST(PolicyTest, IsattyAllowed) { } std::unique_ptr MinimalTestcasePolicy(absl::string_view path = "") { - sandbox2::PolicyBuilder builder; + PolicyBuilder builder; if constexpr (sapi::host_os::IsAndroid()) { builder.AllowDynamicStartup(); @@ -191,7 +191,7 @@ TEST(MinimalTest, MinimalSharedBinaryWorks) { GetTestSourcePath("sandbox2/testcases/minimal_dynamic"); std::vector args = {path}; - sandbox2::PolicyBuilder builder; + PolicyBuilder builder; if constexpr (sapi::host_os::IsAndroid()) { builder.DisableNamespaces(); @@ -216,7 +216,7 @@ TEST(MallocTest, SystemMallocWorks) { GetTestSourcePath("sandbox2/testcases/malloc_system"); std::vector args = {path}; - sandbox2::PolicyBuilder builder; + PolicyBuilder builder; if constexpr (sapi::host_os::IsAndroid()) { builder.DisableNamespaces(); @@ -250,7 +250,7 @@ TEST(MultipleSyscalls, AddPolicyOnSyscallsWorks) { GetTestSourcePath("sandbox2/testcases/add_policy_on_syscalls"); std::vector args = {path}; - sandbox2::PolicyBuilder builder; + PolicyBuilder builder; if constexpr (sapi::host_os::IsAndroid()) { builder.DisableNamespaces(); builder.AllowDynamicStartup(); diff --git a/sandboxed_api/sandbox2/testcases/BUILD.bazel b/sandboxed_api/sandbox2/testcases/BUILD.bazel index e4e8cf4..f872a64 100644 --- a/sandboxed_api/sandbox2/testcases/BUILD.bazel +++ b/sandboxed_api/sandbox2/testcases/BUILD.bazel @@ -236,3 +236,27 @@ cc_binary( "@com_google_absl//absl/strings", ], ) + +cc_binary( + name = "network_proxy", + testonly = True, + srcs = ["network_proxy.cc"], + copts = sapi_platform_copts(), + deps = [ + "//sandboxed_api/sandbox2:client", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2/network_proxy:client", + "//sandboxed_api/util:fileops", + "//sandboxed_api/util:status", + "@com_google_absl//absl/base:log_severity", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:globals", + "@com_google_absl//absl/log:initialize", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/strings:string_view", + ], +) diff --git a/sandboxed_api/sandbox2/testcases/CMakeLists.txt b/sandboxed_api/sandbox2/testcases/CMakeLists.txt index b865336..6298e43 100644 --- a/sandboxed_api/sandbox2/testcases/CMakeLists.txt +++ b/sandboxed_api/sandbox2/testcases/CMakeLists.txt @@ -292,3 +292,32 @@ target_link_libraries(sandbox2_testcase_namespace PRIVATE sapi::file_base sapi::fileops ) + +# sandboxed_api/sandbox2/testcases:network_proxy +add_executable(sandbox2_testcase_network_proxy + network_proxy.cc +) +add_executable(sandbox2::testcase_network_proxy ALIAS sandbox2_testcase_network_proxy) +set_target_properties(sandbox2_testcase_network_proxy PROPERTIES + OUTPUT_NAME network_proxy +) +target_link_libraries(sandbox2_testcase_network_proxy PRIVATE + absl::flags + absl::flags_parse + absl::log + absl::log_globals + absl::log_initialize + absl::log_severity + absl::status + absl::statusor + absl::strings + absl::str_format + sandbox2::client + sandbox2::comms + sandbox2::network_proxy_client + sapi::base + sapi::file_base + sapi::fileops + sapi::status +) + diff --git a/sandboxed_api/sandbox2/testcases/network_proxy.cc b/sandboxed_api/sandbox2/testcases/network_proxy.cc new file mode 100644 index 0000000..f6ab922 --- /dev/null +++ b/sandboxed_api/sandbox2/testcases/network_proxy.cc @@ -0,0 +1,168 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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. +// 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 + +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/log/globals.h" +#include "absl/log/initialize.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "sandboxed_api/sandbox2/client.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/network_proxy/client.h" +#include "sandboxed_api/util/fileops.h" +#include "sandboxed_api/util/status_macros.h" + +ABSL_FLAG(bool, connect_with_handler, true, "Connect using automatic mode."); + +namespace { + +using ::sapi::file_util::fileops::FDCloser; + +static sandbox2::NetworkProxyClient* g_proxy_client; + +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; +} + +absl::Status CommunicationTest(int sock) { + char received[1025] = {0}; + + if (ReadFromFd(sock, reinterpret_cast(received), + sizeof(received) - 1) <= 0) { + return absl::InternalError("Data receiving error"); + } + absl::PrintF("Sandboxee received data from the server:\n\n%s\n", received); + if (strcmp(received, "Hello World\n")) { + return absl::InternalError("Data receiving error"); + } + return absl::OkStatus(); +} + +absl::StatusOr CreateAddres(int port) { + static struct sockaddr_in6 saddr {}; + saddr.sin6_family = AF_INET6; + saddr.sin6_port = htons(port); + + if (int err = inet_pton(AF_INET6, "::1", &saddr.sin6_addr); err <= 0) { + return absl::ErrnoToStatus(errno, "socket()"); + } + return saddr; +} + +absl::Status ConnectWithoutHandler(int s, const struct sockaddr_in6& saddr) { + return g_proxy_client->Connect( + s, reinterpret_cast(&saddr), sizeof(saddr)); +} + +absl::Status ConnectWithHandler(int s, const struct sockaddr_in6& saddr) { + int err = connect(s, reinterpret_cast(&saddr), + sizeof(saddr)); + if (err != 0) { + return absl::InternalError("connect() failed"); + } + + return absl::OkStatus(); +} + +absl::StatusOr ConnectToServer(int port) { + SAPI_ASSIGN_OR_RETURN(struct sockaddr_in6 saddr, CreateAddres(port)); + + FDCloser s(socket(AF_INET6, SOCK_STREAM, 0)); + if (s.get() < 0) { + return absl::ErrnoToStatus(errno, "socket()"); + } + + if (absl::GetFlag(FLAGS_connect_with_handler)) { + SAPI_RETURN_IF_ERROR(ConnectWithHandler(s.get(), saddr)); + } else { + SAPI_RETURN_IF_ERROR(ConnectWithoutHandler(s.get(), saddr)); + } + + LOG(INFO) << "Connected to the server"; + return s; +} + +} // namespace + +int main(int argc, char* argv[]) { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + absl::ParseCommandLine(argc, argv); + absl::InitializeLog(); + + // Set-up the sandbox2::Client object, using a file descriptor (1023). + sandbox2::Comms comms(sandbox2::Comms::kDefaultConnection); + sandbox2::Client sandbox2_client(&comms); + + if (absl::GetFlag(FLAGS_connect_with_handler)) { + if (auto status = sandbox2_client.InstallNetworkProxyHandler(); + !status.ok()) { + LOG(ERROR) << "InstallNetworkProxyHandler() failed: " << status; + return 1; + } + } else { + g_proxy_client = sandbox2_client.GetNetworkProxyClient(); + } + + // Receive port number of the server + int port; + if (!comms.RecvInt32(&port)) { + LOG(ERROR) << "Failed to receive port number"; + return 2; + } + + absl::StatusOr client = ConnectToServer(port); + if (!client.ok()) { + LOG(ERROR) << client.status(); + return 3; + } + + if (auto status = CommunicationTest(client->get()); !status.ok()) { + LOG(ERROR) << status; + return 4; + } + return 0; +}