From 5a4e3f3d294111e5ed10b9561c3749a250b63a49 Mon Sep 17 00:00:00 2001 From: bielec Date: Thu, 20 Feb 2020 07:45:22 -0800 Subject: [PATCH] Now network proxy server supports IP filtering. API to policybuilder is added to make a list of allowed pairs of allowed IP, mask and port where mask and port are optional. PiperOrigin-RevId: 296206385 Change-Id: I53b23122abece1fe318ed4c6a7e37bf3228c8f5f --- sandboxed_api/sandbox2/BUILD.bazel | 46 +-- sandboxed_api/sandbox2/CMakeLists.txt | 31 +- sandboxed_api/sandbox2/client.cc | 1 - sandboxed_api/sandbox2/client.h | 2 +- .../examples/network_proxy/BUILD.bazel | 7 +- .../network_proxy/networkproxy_bin.cc | 3 +- .../network_proxy/networkproxy_sandbox.cc | 7 +- sandboxed_api/sandbox2/ipc.cc | 18 +- sandboxed_api/sandbox2/ipc.h | 5 - sandboxed_api/sandbox2/monitor.cc | 36 ++- sandboxed_api/sandbox2/monitor.h | 14 + .../sandbox2/network_proxy/BUILD.bazel | 80 +++++ .../sandbox2/network_proxy/CMakeLists.txt | 78 +++++ .../client.cc} | 2 +- .../client.h} | 0 .../sandbox2/network_proxy/filtering.cc | 278 ++++++++++++++++++ .../sandbox2/network_proxy/filtering.h | 71 +++++ .../sandbox2/network_proxy/filtering_test.cc | 130 ++++++++ .../server.cc} | 39 ++- .../server.h} | 19 +- sandboxed_api/sandbox2/policy.h | 4 + sandboxed_api/sandbox2/policybuilder.cc | 42 +++ sandboxed_api/sandbox2/policybuilder.h | 8 + sandboxed_api/sandbox2/result.cc | 16 +- sandboxed_api/sandbox2/result.h | 10 + 25 files changed, 829 insertions(+), 118 deletions(-) create mode 100644 sandboxed_api/sandbox2/network_proxy/BUILD.bazel create mode 100644 sandboxed_api/sandbox2/network_proxy/CMakeLists.txt rename sandboxed_api/sandbox2/{network_proxy_client.cc => network_proxy/client.cc} (99%) rename sandboxed_api/sandbox2/{network_proxy_client.h => network_proxy/client.h} (100%) create mode 100644 sandboxed_api/sandbox2/network_proxy/filtering.cc create mode 100644 sandboxed_api/sandbox2/network_proxy/filtering.h create mode 100644 sandboxed_api/sandbox2/network_proxy/filtering_test.cc rename sandboxed_api/sandbox2/{network_proxy_server.cc => network_proxy/server.cc} (62%) rename sandboxed_api/sandbox2/{network_proxy_server.h => network_proxy/server.h} (69%) diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index 7a94c20..46d3be7 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -89,7 +89,7 @@ cc_library( ":syscall", ":util", "//sandboxed_api/util:status", - "@com_google_absl//absl/base", + "//sandboxed_api/util:statusor", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", ], @@ -127,36 +127,6 @@ cc_library( ], ) -cc_library( - name = "network_proxy_server", - srcs = ["network_proxy_server.cc"], - hdrs = ["network_proxy_server.h"], - copts = sapi_platform_copts(), - deps = [ - ":comms", - "//sandboxed_api/sandbox2/util:fileops", - "@com_google_absl//absl/memory", - "@com_google_glog//:glog", - ], -) - -cc_library( - name = "network_proxy_client", - srcs = ["network_proxy_client.cc"], - hdrs = ["network_proxy_client.h"], - copts = sapi_platform_copts(), - visibility = ["//visibility:public"], - deps = [ - ":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", - ], -) - cc_library( name = "ipc", srcs = ["ipc.cc"], @@ -166,8 +136,6 @@ cc_library( ":comms", ":logserver", ":logsink", - ":network_proxy_client", - ":network_proxy_server", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", @@ -186,6 +154,7 @@ cc_library( ":regs", ":syscall", ":violation_cc_proto", + "//sandboxed_api/sandbox2/network_proxy:filtering", "//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/util:flags", "@com_google_absl//absl/base:core_headers", @@ -306,10 +275,10 @@ cc_library( ":client", ":executor", ":comms", - ":violation_cc_proto", - ":forkserver", ":forkserver_cc_proto", ":global_forkserver", + ":violation_cc_proto", + ":forkserver", ":ipc", ":limits", ":logsink", @@ -321,7 +290,6 @@ cc_library( ":result", ":syscall", ":util", - ":network_proxy_client", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", @@ -333,11 +301,15 @@ cc_library( "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", "@org_kernel_libcap//:libcap", + "//sandboxed_api/sandbox2/network_proxy:client", + "//sandboxed_api/sandbox2/network_proxy:filtering", + "//sandboxed_api/sandbox2/network_proxy:server", "//sandboxed_api/sandbox2/unwind", "//sandboxed_api/sandbox2/unwind:unwind_cc_proto", "//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/sandbox2/util:file_base", "//sandboxed_api/sandbox2/util:fileops", + "//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/util:raw_logging", "//sandboxed_api/util:status", "//sandboxed_api/util:statusor", @@ -364,7 +336,7 @@ cc_library( deps = [ ":comms", ":logsink", - ":network_proxy_client", + "//sandboxed_api/sandbox2/network_proxy:client", "//sandboxed_api/sandbox2/util:file_helpers", "//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:strerror", diff --git a/sandboxed_api/sandbox2/CMakeLists.txt b/sandboxed_api/sandbox2/CMakeLists.txt index b2b58fd..fa8c515 100644 --- a/sandboxed_api/sandbox2/CMakeLists.txt +++ b/sandboxed_api/sandbox2/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory(examples) add_subdirectory(unwind) add_subdirectory(util) +add_subdirectory(network_proxy) # sandboxed_api/sandbox2:bpfdisassembler add_library(sandbox2_bpfdisassembler STATIC @@ -121,36 +122,6 @@ target_link_libraries(sandbox2_logsink PUBLIC glog::glog ) -# sandboxed_api/sandbox2:network_proxy_server -add_library(sandbox2_network_proxy_server STATIC - network_proxy_server.cc - network_proxy_server.h -) -add_library(sandbox2::network_proxy_server ALIAS sandbox2_network_proxy_server) -target_link_libraries(sandbox2_network_proxy_server PRIVATE - absl::memory - glog::glog - sandbox2::comms - sandbox2::fileops - sapi::base -) - -# sandboxed_api/sandbox2:network_proxy_client -add_library(sandbox2_network_proxy_client STATIC - network_proxy_client.cc - network_proxy_client.h -) -add_library(sandbox2::network_proxy_client ALIAS sandbox2_network_proxy_client) -target_link_libraries(sandbox2_network_proxy_client PRIVATE - absl::strings - absl::synchronization - glog::glog - sandbox2::comms - sandbox2::strerror - sapi::base - sapi::status -) - # sandboxed_api/sandbox2:ipc add_library(sandbox2_ipc STATIC ipc.cc diff --git a/sandboxed_api/sandbox2/client.cc b/sandboxed_api/sandbox2/client.cc index f8490eb..01289eb 100644 --- a/sandboxed_api/sandbox2/client.cc +++ b/sandboxed_api/sandbox2/client.cc @@ -40,7 +40,6 @@ #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" diff --git a/sandboxed_api/sandbox2/client.h b/sandboxed_api/sandbox2/client.h index d1a4c2c..3518ff8 100644 --- a/sandboxed_api/sandbox2/client.h +++ b/sandboxed_api/sandbox2/client.h @@ -25,7 +25,7 @@ #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/logsink.h" -#include "sandboxed_api/sandbox2/network_proxy_client.h" +#include "sandboxed_api/sandbox2/network_proxy/client.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel index 2a3faed..ad15d87 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel +++ b/sandboxed_api/sandbox2/examples/network_proxy/BUILD.bazel @@ -14,10 +14,10 @@ # The 'network proxy' example demonstrates how to use network proxy server. -licenses(["notice"]) - load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts") +licenses(["notice"]) + # Executor cc_binary( name = "networkproxy_sandbox", @@ -27,6 +27,7 @@ cc_binary( deps = [ "//sandboxed_api/sandbox2", "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2/network_proxy:filtering", "//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:runfiles", @@ -42,7 +43,7 @@ cc_binary( deps = [ "//sandboxed_api/sandbox2:client", "//sandboxed_api/sandbox2:comms", - "//sandboxed_api/sandbox2:network_proxy_client", + "//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/networkproxy_bin.cc b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc index cd57021..d859f67 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_bin.cc @@ -14,7 +14,7 @@ #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/network_proxy/client.h" #include "sandboxed_api/sandbox2/util/fileops.h" static ssize_t ReadFromFd(int fd, uint8_t* buf, size_t size) { @@ -113,5 +113,6 @@ int main(int argc, char** argv) { 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 index 46aae97..ef80b00 100644 --- a/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc +++ b/sandboxed_api/sandbox2/examples/network_proxy/networkproxy_sandbox.cc @@ -42,6 +42,8 @@ std::unique_ptr GetPolicy(absl::string_view sandboxee_path) { .AllowSyscall(__NR_munmap) .AllowSyscall(__NR_getpid) .AddNetworkProxyHandlerPolicy() + .AllowTcMalloc() + .AllowIPv6("::1") .AddLibrariesForBinary(sandboxee_path) .BuildOrDie(); } @@ -129,10 +131,7 @@ int main(int argc, char** argv) { // 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(); + .set_cwd("/"); executor ->limits() // Remove restrictions on the size of address-space of sandboxed diff --git a/sandboxed_api/sandbox2/ipc.cc b/sandboxed_api/sandbox2/ipc.cc index 7c77654..7a25ff5 100644 --- a/sandboxed_api/sandbox2/ipc.cc +++ b/sandboxed_api/sandbox2/ipc.cc @@ -17,16 +17,12 @@ #include "sandboxed_api/sandbox2/ipc.h" #include -#include - -#include // NOLINT(build/c++11) +#include #include #include "absl/memory/memory.h" #include "sandboxed_api/sandbox2/logserver.h" #include "sandboxed_api/sandbox2/logsink.h" -#include "sandboxed_api/sandbox2/network_proxy_client.h" -#include "sandboxed_api/sandbox2/network_proxy_server.h" namespace sandbox2 { @@ -103,16 +99,4 @@ void IPC::EnableLogServer() { log_thread.detach(); } -void IPC::EnableNetworkProxyServer() { - int fd = ReceiveFd(NetworkProxyClient::kFDName); - - auto proxy_server = [fd]() { - NetworkProxyServer network_proxy_server(fd); - network_proxy_server.Run(); - }; - - std::thread proxy_thread{proxy_server}; - proxy_thread.detach(); -} - } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/ipc.h b/sandboxed_api/sandbox2/ipc.h index 52c4704..69f8421 100644 --- a/sandboxed_api/sandbox2/ipc.h +++ b/sandboxed_api/sandbox2/ipc.h @@ -23,7 +23,6 @@ #include #include -#include "absl/base/macros.h" #include "absl/strings/string_view.h" #include "sandboxed_api/sandbox2/comms.h" @@ -60,10 +59,6 @@ class IPC final { // Client::SendLogsToSupervisor in the sandboxee. void EnableLogServer(); - // Enable network proxy server, this will start a thread in the sandbox - // that waits for connection requests from the sandboxee. - void EnableNetworkProxyServer(); - private: friend class Executor; friend class Monitor; diff --git a/sandboxed_api/sandbox2/monitor.cc b/sandboxed_api/sandbox2/monitor.cc index 1e62f86..baf363b 100644 --- a/sandboxed_api/sandbox2/monitor.cc +++ b/sandboxed_api/sandbox2/monitor.cc @@ -53,6 +53,7 @@ #include "sandboxed_api/sandbox2/limits.h" #include "sandboxed_api/sandbox2/mounts.h" #include "sandboxed_api/sandbox2/namespace.h" +#include "sandboxed_api/sandbox2/network_proxy/server.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/regs.h" #include "sandboxed_api/sandbox2/result.h" @@ -140,6 +141,9 @@ Monitor::~Monitor() { if (log_file_) { std::fclose(log_file_); } + if (network_proxy_server_) { + network_proxy_thread_.join(); + } } namespace { @@ -198,6 +202,10 @@ void Monitor::Run() { // sandbox master/monitor, which ptrace_attach'es to the child. int clone_flags = CLONE_UNTRACED; + if (policy_->allowed_hosts_) { + EnableNetworkProxyServer(); + } + // Get PID of the sandboxee. pid_t init_pid = 0; Namespace* ns = policy_->GetNamespace(); @@ -349,6 +357,14 @@ void Monitor::MainLoop(sigset_t* sset) { KillSandboxee(); } + if (network_proxy_server_ && + network_proxy_server_->violation_occurred_.load( + std::memory_order_acquire) && + !network_violation_) { + network_violation_ = true; + KillSandboxee(); + } + // It should be a non-blocking operation (hence WNOHANG), so this function // returns quickly if there are no events to be processed. // Prioritize main pid to avoid resource starvation @@ -401,7 +417,10 @@ void Monitor::MainLoop(sigset_t* sset) { VLOG(1) << "PID: " << ret << " terminated with signal: " << util::GetSignalName(WTERMSIG(status)); if (ret == pid_) { - if (external_kill_) { + if (network_violation_) { + SetExitStatusCode(Result::VIOLATION, Result::VIOLATION_NETWORK); + result_.SetNetworkViolation(network_proxy_server_->violation_msg_); + } else if (external_kill_) { SetExitStatusCode(Result::EXTERNAL_KILL, 0); } else if (timed_out_) { SetExitStatusCode(Result::TIMEOUT, 0); @@ -794,7 +813,10 @@ void Monitor::EventPtraceExit(pid_t pid, int event_msg) { // 3) Regular signal/other exit cause. if (pid == pid_) { VLOG(1) << "PID: " << pid << " main special exit"; - if (external_kill_) { + if (network_violation_) { + SetExitStatusCode(Result::VIOLATION, Result::VIOLATION_NETWORK); + result_.SetNetworkViolation(network_proxy_server_->violation_msg_); + } else if (external_kill_) { SetExitStatusCode(Result::EXTERNAL_KILL, 0); } else if (timed_out_) { SetExitStatusCode(Result::TIMEOUT, 0); @@ -926,4 +948,14 @@ void Monitor::LogSyscallViolationExplanation(const Syscall& syscall) const { } } +void Monitor::EnableNetworkProxyServer() { + int fd = ipc_->ReceiveFd(NetworkProxyClient::kFDName); + + network_proxy_server_ = absl::make_unique( + fd, &policy_->allowed_hosts_.value(), pthread_self()); + + network_proxy_thread_ = std::thread(&NetworkProxyServer::Run, + network_proxy_server_.get()); +} + } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/monitor.h b/sandboxed_api/sandbox2/monitor.h index 61358c8..069d93e 100644 --- a/sandboxed_api/sandbox2/monitor.h +++ b/sandboxed_api/sandbox2/monitor.h @@ -26,11 +26,13 @@ #include #include #include +#include #include "absl/synchronization/notification.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" +#include "sandboxed_api/sandbox2/network_proxy/server.h" #include "sandboxed_api/sandbox2/notify.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/regs.h" @@ -139,6 +141,10 @@ class Monitor final { // Processes stop path. void EventPtraceStop(pid_t pid, int stopsig); + // Enable network proxy server, this will start a thread in the sandbox + // that waits for connection requests from the sandboxee. + void EnableNetworkProxyServer(); + // Internal objects, owned by the Sandbox2 object. Executor* executor_; Notify* notify_; @@ -168,6 +174,8 @@ class Monitor final { std::atomic deadline_millis_{0}; // Was external kill sent to the sandboxee bool external_kill_ = false; + // Network violation occurred and process of killing sandboxee started + bool network_violation_ = false; // Is the sandboxee timed out bool timed_out_ = false; // Should we dump the main sandboxed PID's stack? @@ -178,6 +186,12 @@ class Monitor final { // Log file specified by // --sandbox_danger_danger_permit_all_and_log flag. FILE* log_file_ = nullptr; + + // Handle to the class responsible for proxying and validating connect() + // requests. + std::unique_ptr network_proxy_server_; + + std::thread network_proxy_thread_; }; } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy/BUILD.bazel b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel new file mode 100644 index 0000000..3a3a9d6 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy/BUILD.bazel @@ -0,0 +1,80 @@ +# Copyright 2019 Google LLC. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts") + +package(default_visibility = [ + "//sandboxed_api/sandbox2:__subpackages__", +]) + +licenses(["notice"]) # Apache 2.0 + +cc_library( + name = "server", + srcs = ["server.cc"], + hdrs = ["server.h"], + copts = sapi_platform_copts(), + deps = [ + ":filtering", + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2/util:fileops", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "@com_google_glog//:glog", + ], +) + +cc_library( + name = "client", + srcs = ["client.cc"], + hdrs = ["client.h"], + copts = sapi_platform_copts(), + visibility = ["//visibility:public"], + deps = [ + "//sandboxed_api/sandbox2: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", + ], +) + +cc_library( + name = "filtering", + srcs = ["filtering.cc"], + hdrs = ["filtering.h"], + copts = sapi_platform_copts(), + deps = [ + "//sandboxed_api/sandbox2:comms", + "//sandboxed_api/sandbox2/util:strerror", + "//sandboxed_api/util:status", + "//sandboxed_api/util:statusor", + "@com_google_absl//absl/strings", + "@com_google_glog//:glog", + ], +) + +cc_test( + name = "filtering_test", + srcs = ["filtering_test.cc"], + copts = sapi_platform_copts(), + deps = [ + ":filtering", + "//sandboxed_api/sandbox2:testing", + "//sandboxed_api/util:status_matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt b/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt new file mode 100644 index 0000000..8ae6c40 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy/CMakeLists.txt @@ -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. + +# sandboxed_api/sandbox2/network_proxy:server +add_library(sandbox2_network_proxy_server STATIC + server.cc + server.h +) +add_library(sandbox2::network_proxy_server ALIAS sandbox2_network_proxy_server) +target_link_libraries(sandbox2_network_proxy_server PRIVATE + absl::memory + glog::glog + sandbox2::comms + sandbox2::fileops + sandbox2::network_proxy_filtering + sapi::base +) + +# sandboxed_api/sandbox2/network_proxy:filtering +add_library(sandbox2_network_proxy_filtering STATIC + filtering.cc + filtering.h +) +add_library(sandbox2::network_proxy_filtering ALIAS sandbox2_network_proxy_filtering) +target_link_libraries(sandbox2_network_proxy_filtering PRIVATE + absl::memory + glog::glog + sandbox2::comms + sandbox2::fileops + sapi::base + PUBLIC sapi::status + sapi::statusor +) + +# sandboxed_api/sandbox2/network_proxy:client +add_library(sandbox2_network_proxy_client STATIC + client.cc + client.h +) +add_library(sandbox2::network_proxy_client ALIAS sandbox2_network_proxy_client) +target_link_libraries(sandbox2_network_proxy_client PRIVATE + absl::strings + absl::synchronization + glog::glog + sandbox2::comms + sandbox2::strerror + sapi::base + sapi::status +) + +if(SAPI_ENABLE_TESTS) + # sandboxed_api/sandbox2/network_proxy:filtering_test + add_executable(filtering_test + filtering_test.cc + ) + target_link_libraries(filtering_test PRIVATE + absl::strings + glog::glog + gflags::gflags + sandbox2::network_proxy_filtering + sandbox2::testing + sapi::base + sapi::status_matchers + sapi::test_main + ) + gtest_discover_tests(filtering_test) +endif() diff --git a/sandboxed_api/sandbox2/network_proxy_client.cc b/sandboxed_api/sandbox2/network_proxy/client.cc similarity index 99% rename from sandboxed_api/sandbox2/network_proxy_client.cc rename to sandboxed_api/sandbox2/network_proxy/client.cc index cae5d65..877fac3 100644 --- a/sandboxed_api/sandbox2/network_proxy_client.cc +++ b/sandboxed_api/sandbox2/network_proxy/client.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "sandboxed_api/sandbox2/network_proxy_client.h" +#include "sandboxed_api/sandbox2/network_proxy/client.h" #include #include diff --git a/sandboxed_api/sandbox2/network_proxy_client.h b/sandboxed_api/sandbox2/network_proxy/client.h similarity index 100% rename from sandboxed_api/sandbox2/network_proxy_client.h rename to sandboxed_api/sandbox2/network_proxy/client.h diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.cc b/sandboxed_api/sandbox2/network_proxy/filtering.cc new file mode 100644 index 0000000..4099b9c --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy/filtering.cc @@ -0,0 +1,278 @@ +// Copyright 2019 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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/filtering.h" + +#include + +#include +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "sandboxed_api/sandbox2/util/strerror.h" +#include "sandboxed_api/util/status.h" +#include "sandboxed_api/util/status_macros.h" + +namespace sandbox2 { + +static sapi::StatusOr Addr6ToString( + const struct sockaddr_in6* saddr) { + char addr[INET6_ADDRSTRLEN]; + int port = htons(saddr->sin6_port); + if (!inet_ntop(AF_INET6, &saddr->sin6_addr, addr, sizeof addr)) { + return sapi::InternalError( + "Error in converting sockaddr_in6 addres to string"); + } + return absl::StrCat("IP: ", addr, ", port: ", port); +} + +// Converts sockaddr_in structure into a string IPv4 representation. +static sapi::StatusOr Addr4ToString( + const struct sockaddr_in* saddr) { + char addr[INET_ADDRSTRLEN]; + int port = htons(saddr->sin_port); + if (!inet_ntop(AF_INET, &saddr->sin_addr, addr, sizeof addr)) { + return sapi::InternalError( + "Error in converting sockaddr_in addres to string"); + } + return absl::StrCat("IP: ", addr, ", port: ", port); +} + +// Converts sockaddr_in6 structure into a string IPv6 representation. +sapi::StatusOr AddrToString(const struct sockaddr* saddr) { + switch (saddr->sa_family) { + case AF_INET: + return Addr4ToString(reinterpret_cast(saddr)); + case AF_INET6: + return Addr6ToString(reinterpret_cast(saddr)); + default: + return sapi::InternalError( + absl::StrCat("Unexpected sa_family value: ", saddr->sa_family)); + } +} + +static sapi::Status IPStringToAddr(const std::string& ip, int address_family, + void* addr) { + int err = inet_pton(address_family, ip.c_str(), addr); + if (err == 0) { + return sapi::InvalidArgumentError(absl::StrCat("Invalid address: ", ip)); + } + if (err == -1) { + return sapi::InternalError( + absl::StrCat("inet_pton() failed for ", ip, ": ", StrError(errno))); + } + + return sapi::OkStatus(); +} + +// Parses a string of type IP or IP/mask or IP/cidr and saves appropriate +// values in output arguments. +static sapi::Status ParseIpAndMask(const std::string& ip_and_mask, + std::string* ip, std::string* mask, + uint32_t* cidr) { + // mask is checked later because only IPv4 format supports mask + if (ip == nullptr || cidr == nullptr) { + return sapi::InvalidArgumentError( + "ip and cidr arguments of ParseIpAndMask cannot be nullptr"); + } + *cidr = 0; + + std::vector ip_and_mask_split = + absl::StrSplit(ip_and_mask, absl::MaxSplits('/', 1)); + + *ip = ip_and_mask_split[0]; + if (ip_and_mask_split.size() == 1) { + return sapi::OkStatus(); + } + std::string mask_or_cidr = ip_and_mask_split[1]; + + const bool has_dot = mask_or_cidr.find(".") == absl::string_view::npos; + if (has_dot) { // mask_or_cidr is cidr + bool res = absl::SimpleAtoi(mask_or_cidr, cidr); + if (!res || !*cidr) { + return sapi::InvalidArgumentError( + absl::StrCat(mask_or_cidr, " is not a correct cidr")); + } + } else { + if (mask == nullptr) { + return sapi::InvalidArgumentError( + "mask argument of ParseIpAndMask cannot be NULL in this case"); + } + *mask = std::string(mask_or_cidr); + } + return sapi::OkStatus(); +} + +static sapi::Status CidrToIn6Addr(uint32_t cidr, in6_addr* addr) { + if (cidr > 128) { + return sapi::InvalidArgumentError( + absl::StrCat(cidr, " is not a correct cidr")); + } + + memset(addr, 0, sizeof(*addr)); + + int i = 0; + while (cidr >= 8) { + addr->s6_addr[i++] = 0xff; + cidr -= 8; + } + if (cidr) { + uint8_t tmp = 0x0; + while (cidr--) { + tmp >>= 1; + tmp |= 0x80; + } + addr->s6_addr[i] = tmp; + } + return sapi::OkStatus(); +} + +static sapi::Status CidrToInAddr(uint32_t cidr, in_addr* addr) { + if (cidr > 32) { + return sapi::InvalidArgumentError( + absl::StrCat(cidr, " is not a correct cidr")); + } + + memset(addr, 0, sizeof(*addr)); + + uint32_t tmp = 0x0; + while (cidr--) { + tmp >>= 1; + tmp |= 0x80000000; + } + addr->s_addr = htonl(tmp); + return sapi::OkStatus(); +} + +static bool IsIPv4MaskCorrect(in_addr_t m) { + m = ntohl(m); + if (m == 0) { + return false; + } + m = ~m + 1; + return !(m & (m - 1)); +} + +sapi::Status AllowedHosts::AllowIPv4(const std::string& ip_and_mask, + uint32_t port) { + std::string ip, mask; + uint32_t cidr; + SAPI_RETURN_IF_ERROR(ParseIpAndMask(ip_and_mask, &ip, &mask, &cidr)); + SAPI_RETURN_IF_ERROR(AllowIPv4(ip, mask, cidr, port)); + + return sapi::OkStatus(); +} + +sapi::Status AllowedHosts::AllowIPv6(const std::string& ip_and_mask, + uint32_t port) { + std::string ip; + uint32_t cidr; + SAPI_RETURN_IF_ERROR(ParseIpAndMask(ip_and_mask, &ip, NULL, &cidr)); + SAPI_RETURN_IF_ERROR(AllowIPv6(ip, cidr, port)); + return sapi::OkStatus(); +} + +sapi::Status AllowedHosts::AllowIPv4(const std::string& ip, + const std::string& mask, uint32_t cidr, + uint32_t port) { + in_addr addr{}; + in_addr m{}; + + if (mask.length()) { + SAPI_RETURN_IF_ERROR(IPStringToAddr(mask, AF_INET, &m)); + + if (!IsIPv4MaskCorrect(m.s_addr)) { + return sapi::InvalidArgumentError( + absl::StrCat(mask, " is not a correct mask")); + } + + } else { + if (cidr > 32) { + return sapi::InvalidArgumentError( + absl::StrCat(cidr, " is not a correct cidr")); + } + if (!cidr) { + cidr = 32; + } + + SAPI_RETURN_IF_ERROR(CidrToInAddr(cidr, &m)); + } + + SAPI_RETURN_IF_ERROR(IPStringToAddr(ip, AF_INET, &addr)); + allowed_IPv4_.emplace_back(addr.s_addr, m.s_addr, htons(port)); + + return sapi::OkStatus(); +} + +sapi::Status AllowedHosts::AllowIPv6(const std::string& ip, uint32_t cidr, + uint32_t port) { + if (cidr == 0) { + cidr = 128; + } + + in6_addr addr{}; + SAPI_RETURN_IF_ERROR(IPStringToAddr(ip, AF_INET6, &addr)); + + in6_addr m; + SAPI_RETURN_IF_ERROR(CidrToIn6Addr(cidr, &m)); + + allowed_IPv6_.emplace_back(addr, m, htons(port)); + return sapi::OkStatus(); +} + +bool AllowedHosts::IsHostAllowed(const struct sockaddr* saddr) const { + switch (saddr->sa_family) { + case AF_INET: + return IsIPv4Allowed(reinterpret_cast(saddr)); + case AF_INET6: + return IsIPv6Allowed(reinterpret_cast(saddr)); + default: + LOG(FATAL) << absl::StrCat("Unexpected sa_family value: ", + saddr->sa_family); + return false; + } +} + +bool AllowedHosts::IsIPv6Allowed(const struct sockaddr_in6* saddr) const { + auto result = std::find_if( + allowed_IPv6_.begin(), allowed_IPv6_.end(), [saddr](const IPv6& entry) { + for (int i = 0; i < 4; i++) { + if ((entry.ip.__in6_u.__u6_addr32[i] & + entry.mask.__in6_u.__u6_addr32[i]) != + (saddr->sin6_addr.__in6_u.__u6_addr32[i] & + entry.mask.__in6_u.__u6_addr32[i])) { + return false; + } + } + if (!entry.port || (entry.port == saddr->sin6_port)) { + return true; + } + return false; + }); + + return result != allowed_IPv6_.end(); +} + +bool AllowedHosts::IsIPv4Allowed(const struct sockaddr_in* saddr) const { + auto result = std::find_if( + allowed_IPv4_.begin(), allowed_IPv4_.end(), [saddr](const IPv4& entry) { + return ((entry.ip & entry.mask) == + (saddr->sin_addr.s_addr & entry.mask)) && + (!entry.port || (entry.port == saddr->sin_port)); + }); + + return result != allowed_IPv4_.end(); +} + +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy/filtering.h b/sandboxed_api/sandbox2/network_proxy/filtering.h new file mode 100644 index 0000000..5de3816 --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy/filtering.h @@ -0,0 +1,71 @@ +// Copyright 2019 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SANDBOXED_API_SANDBOX2_NETWORK_PROXY_FILTERING_H_ +#define SANDBOXED_API_SANDBOX2_NETWORK_PROXY_FILTERING_H_ + +#include + +#include + +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/util/statusor.h" + +namespace sandbox2 { + +// Converts sockaddr_in or sockaddr_in6 structure into a string +// representation. +sapi::StatusOr AddrToString(const struct sockaddr* saddr); + +struct IPv4 { + in_addr_t ip; + in_addr_t mask; + uint32_t port; + IPv4(in_addr_t IP, in_addr_t mask, uint32_t port) + : ip(IP), mask(mask), port(port) {} +}; + +struct IPv6 { + in6_addr ip; + in6_addr mask; + uint32_t port; + IPv6(in6_addr IP, in6_addr mask, uint32_t port) + : ip(IP), mask(mask), port(port) {} +}; + +// Keeps a list of allowed pairs of IP, mask and port. Port equal to 0 means +// that all ports are allowed. +class AllowedHosts { + public: + // ip_and_mask should have one of following formats: IP, IP/mask, IP/cidr. + sapi::Status AllowIPv4(const std::string& ip_and_mask, uint32_t port = 0); + // ip_and_mask should have following format: IP or IP/cidr. + sapi::Status AllowIPv6(const std::string& ip_and_mask, uint32_t port = 0); + // Checks if this host is allowed. + bool IsHostAllowed(const struct sockaddr* saddr) const; + + private: + sapi::Status AllowIPv4(const std::string& ip, const std::string& mask, + uint32_t cidr, uint32_t port); + sapi::Status AllowIPv6(const std::string& ip, uint32_t cidr, uint32_t port); + bool IsIPv4Allowed(const struct sockaddr_in* saddr) const; + bool IsIPv6Allowed(const struct sockaddr_in6* saddr) const; + + std::vector allowed_IPv4_; + std::vector allowed_IPv6_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_NETWORK_PROXY_FILTERING_H_ diff --git a/sandboxed_api/sandbox2/network_proxy/filtering_test.cc b/sandboxed_api/sandbox2/network_proxy/filtering_test.cc new file mode 100644 index 0000000..5e9562f --- /dev/null +++ b/sandboxed_api/sandbox2/network_proxy/filtering_test.cc @@ -0,0 +1,130 @@ +// Copyright 2019 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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/filtering.h" + +#include +#include +#include + +#include +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "sandboxed_api/sandbox2/testing.h" +#include "sandboxed_api/util/status_matchers.h" + +using ::sapi::IsOk; +using ::testing::IsFalse; +using ::testing::IsTrue; + +namespace sandbox2 { +namespace { + +static struct sockaddr* PrepareIpv6(const std::string& ip, uint32_t port = 80) { + static struct sockaddr_in6 saddr {}; + memset(&saddr, 0, sizeof(saddr)); + + saddr.sin6_family = AF_INET6; + saddr.sin6_port = htons(port); + + int err = inet_pton(AF_INET6, ip.c_str(), &saddr.sin6_addr); + CHECK_GE(err, -1); + + return reinterpret_cast(&saddr); +} + +static struct sockaddr* PrepareIpv4(const std::string& ip, uint32_t port = 80) { + static struct sockaddr_in saddr {}; + memset(&saddr, 0, sizeof(saddr)); + + saddr.sin_family = AF_INET; + saddr.sin_port = htons(port); + + int err = inet_pton(AF_INET, ip.c_str(), &saddr.sin_addr); + CHECK_GE(err, -1); + + return reinterpret_cast(&saddr); +} + +TEST(FilteringTest, Basic) { + sandbox2::AllowedHosts allowed_hosts; + + // Create rules + EXPECT_THAT(allowed_hosts.AllowIPv4("127.0.0.1"), IsOk()); + EXPECT_THAT(allowed_hosts.AllowIPv4("127.0.0.2", 33), IsOk()); + EXPECT_THAT(allowed_hosts.AllowIPv4("120.120.120.120/255.255.255.0"), IsOk()); + EXPECT_THAT(allowed_hosts.AllowIPv4("130.130.130.130/255.255.252.0", 1000), + IsOk()); + EXPECT_THAT(allowed_hosts.AllowIPv4("140.140.140.140/8"), IsOk()); + EXPECT_THAT(allowed_hosts.AllowIPv4("150.150.150.150/10", 123), IsOk()); + + EXPECT_THAT(allowed_hosts.AllowIPv6("::2"), IsOk()); + EXPECT_THAT(allowed_hosts.AllowIPv6("::1", 80), IsOk()); + EXPECT_THAT(allowed_hosts.AllowIPv6("0:1234:0:0:0:0:0:0/32"), IsOk()); + EXPECT_THAT(allowed_hosts.AllowIPv6("0:5678:0:0:0:0:0:0/46", 70), IsOk()); + + // IPv4 tests + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("130.0.0.3")), + testing::IsFalse()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("127.0.0.1")), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("127.0.0.2")), + testing::IsFalse()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("127.0.0.2", 33)), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("120.120.120.255")), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("120.120.121.120")), + testing::IsFalse()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("130.130.128.130", 1000)), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("130.130.132.134", 1000)), + testing::IsFalse()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("130.130.128.130", 1001)), + testing::IsFalse()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("140.0.140.140")), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("141.140.140.140")), + testing::IsFalse()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("150.182.150.150", 123)), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv4("150.214.150.150", 123)), + testing::IsFalse()); + + // IPv6 tests + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv6("::3")), + testing::IsFalse()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv6("::2")), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv6("::1")), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv6("::1", 81)), + testing::IsFalse()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv6("0:1234:ffff:0:0:0:0:0")), + testing::IsTrue()); + EXPECT_THAT(allowed_hosts.IsHostAllowed(PrepareIpv6("0:1233:0000:0:0:0:0:0")), + testing::IsFalse()); + EXPECT_THAT( + allowed_hosts.IsHostAllowed(PrepareIpv6("0:5678:0002:0:0:0:0:0", 70)), + testing::IsTrue()); + EXPECT_THAT( + allowed_hosts.IsHostAllowed(PrepareIpv6("0:5678:0004:0:0:0:0:0", 70)), + testing::IsFalse()); + EXPECT_THAT( + allowed_hosts.IsHostAllowed(PrepareIpv6("0:5678:0000:0:0:0:0:0", 2222)), + testing::IsFalse()); +} + +} // namespace +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy_server.cc b/sandboxed_api/sandbox2/network_proxy/server.cc similarity index 62% rename from sandboxed_api/sandbox2/network_proxy_server.cc rename to sandboxed_api/sandbox2/network_proxy/server.cc index e0f7552..c456306 100644 --- a/sandboxed_api/sandbox2/network_proxy_server.cc +++ b/sandboxed_api/sandbox2/network_proxy/server.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "sandboxed_api/sandbox2/network_proxy_server.h" +#include "sandboxed_api/sandbox2/network_proxy/server.h" #include #include @@ -20,6 +20,7 @@ #include #include #include + #include #include @@ -29,8 +30,13 @@ namespace sandbox2 { -NetworkProxyServer::NetworkProxyServer(int fd) - : comms_{absl::make_unique(fd)}, fatal_error_{false} {} +NetworkProxyServer::NetworkProxyServer(int fd, AllowedHosts* allowed_hosts, + pthread_t monitor_thread_id) + : violation_occurred_(false), + comms_{absl::make_unique(fd)}, + fatal_error_(false), + monitor_thread_id_(monitor_thread_id), + allowed_hosts_(allowed_hosts) {} void NetworkProxyServer::ProcessConnectRequest() { std::vector addr; @@ -39,18 +45,22 @@ void NetworkProxyServer::ProcessConnectRequest() { return; } - const struct sockaddr_in* saddr = - reinterpret_cast(addr.data()); + const struct sockaddr* saddr = reinterpret_cast(addr.data()); // Only IPv4 TCP and IPv6 TCP are supported. - if (!((addr.size() == sizeof(sockaddr_in) && saddr->sin_family == AF_INET) || + if (!((addr.size() == sizeof(sockaddr_in) && saddr->sa_family == AF_INET) || (addr.size() == sizeof(sockaddr_in6) && - saddr->sin_family == AF_INET6))) { + saddr->sa_family == AF_INET6))) { SendError(EINVAL); return; } - int new_socket = socket(saddr->sin_family, SOCK_STREAM, 0); + if (!allowed_hosts_->IsHostAllowed(saddr)) { + NotifyViolation(saddr); + return; + } + + int new_socket = socket(saddr->sa_family, SOCK_STREAM, 0); if (new_socket < 0) { SendError(errno); return; @@ -73,7 +83,8 @@ void NetworkProxyServer::ProcessConnectRequest() { } void NetworkProxyServer::Run() { - while (!fatal_error_) { + while (!fatal_error_ && + !violation_occurred_.load(std::memory_order_relaxed)) { ProcessConnectRequest(); } LOG(INFO) @@ -92,4 +103,14 @@ void NetworkProxyServer::NotifySuccess() { } } +void NetworkProxyServer::NotifyViolation(const struct sockaddr* saddr) { + sapi::StatusOr result = AddrToString(saddr); + if (result.ok()) { + violation_msg_ = result.ValueOrDie(); + } else { + violation_msg_ = std::string(result.status().message()); + } + violation_occurred_.store(true, std::memory_order_release); + pthread_kill(monitor_thread_id_, SIGCHLD); +} } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/network_proxy_server.h b/sandboxed_api/sandbox2/network_proxy/server.h similarity index 69% rename from sandboxed_api/sandbox2/network_proxy_server.h rename to sandboxed_api/sandbox2/network_proxy/server.h index 55f6c5b..e601a14 100644 --- a/sandboxed_api/sandbox2/network_proxy_server.h +++ b/sandboxed_api/sandbox2/network_proxy/server.h @@ -18,15 +18,18 @@ #include #include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/network_proxy/filtering.h" namespace sandbox2 { // This is a proxy server that spawns connected sockets on requests. // Then it sends the file descriptor to the requestor. It is used to get around -// limitations created by network namespaces. +// limitations created by network namespaces. It also contains a set of rules +// of allowed hosts. class NetworkProxyServer { public: - explicit NetworkProxyServer(int fd); + NetworkProxyServer(int fd, AllowedHosts* allowed_hosts, + pthread_t monitor_thread_id); NetworkProxyServer(const NetworkProxyServer&) = delete; NetworkProxyServer& operator=(const NetworkProxyServer&) = delete; @@ -34,6 +37,11 @@ class NetworkProxyServer { // Starts handling incoming connection requests. void Run(); + // When the network rules were violated violation_occurred_ is set and + // violation_msg_ contains details about the host. + std::atomic violation_occurred_; + std::string violation_msg_; + private: // Notifies the network proxy client about the error and sends its code. void SendError(int saved_errno); @@ -44,8 +52,15 @@ class NetworkProxyServer { // Serves connection requests from the network proxy client. void ProcessConnectRequest(); + // Throw a violation when the network rules are subverted. + void NotifyViolation(const struct sockaddr* saddr); + std::unique_ptr comms_; bool fatal_error_; + pthread_t monitor_thread_id_; + + // Contains list of allowed to connect hosts. + AllowedHosts* allowed_hosts_; }; } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/policy.h b/sandboxed_api/sandbox2/policy.h index b507f22..d4a0ca1 100644 --- a/sandboxed_api/sandbox2/policy.h +++ b/sandboxed_api/sandbox2/policy.h @@ -31,6 +31,7 @@ #include "absl/base/macros.h" #include "absl/types/optional.h" #include "sandboxed_api/sandbox2/namespace.h" +#include "sandboxed_api/sandbox2/network_proxy/filtering.h" #include "sandboxed_api/sandbox2/syscall.h" #include "sandboxed_api/sandbox2/violation.pb.h" @@ -104,6 +105,9 @@ class Policy final { // Get a policy which would allow the Monitor module to track all syscalls. std::vector GetTrackingPolicy() const; + // Contains a list of hosts the sandboxee is allowed to connect to. + absl::optional allowed_hosts_; + friend class Monitor; friend class PolicyBuilder; friend class PolicyBuilderPeer; // For testing diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc index bde3f6f..b458618 100644 --- a/sandboxed_api/sandbox2/policybuilder.cc +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -699,6 +699,7 @@ sapi::StatusOr> PolicyBuilder::TryBuild() { StoreDescription(pb_description.get()); output->policy_builder_description_ = std::move(pb_description); + output->allowed_hosts_ = std::move(allowed_hosts_); already_built_ = true; return std::move(output); } @@ -847,6 +848,15 @@ PolicyBuilder& PolicyBuilder::CollectStacktracesOnKill(bool enable) { } PolicyBuilder& PolicyBuilder::AddNetworkProxyPolicy() { + if (allowed_hosts_) { + SetError(sapi::FailedPreconditionError( + "AddNetworkProxyPolicy or AddNetworkProxyHandlerPolicy can be called " + "at most once")); + return *this; + } + + allowed_hosts_ = AllowedHosts(); + AllowFutexOp(FUTEX_WAKE); AllowFutexOp(FUTEX_WAIT); AllowFutexOp(FUTEX_WAIT_BITSET); @@ -919,4 +929,36 @@ void PolicyBuilder::StoreDescription(PolicyBuilderDescription* pb_description) { } } +PolicyBuilder& PolicyBuilder::AllowIPv4(const std::string& ip_and_mask, + uint32_t port) { + if (!allowed_hosts_) { + SetError(sapi::FailedPreconditionError( + "AddNetworkProxyPolicy or AddNetworkProxyHandlerPolicy must be called " + "before adding IP rules")); + return *this; + } + + sapi::Status status = allowed_hosts_->AllowIPv4(ip_and_mask, port); + if (!status.ok()) { + SetError(status); + } + return *this; +} + +PolicyBuilder& PolicyBuilder::AllowIPv6(const std::string& ip_and_mask, + uint32_t port) { + if (!allowed_hosts_) { + SetError(sapi::FailedPreconditionError( + "AddNetworkProxyPolicy or AddNetworkProxyHandlerPolicy must be called " + "before adding IP rules")); + return *this; + } + + sapi::Status status = allowed_hosts_->AllowIPv6(ip_and_mask, port); + if (!status.ok()) { + SetError(status); + } + return *this; +} + } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h index b9d48db..c4997a7 100644 --- a/sandboxed_api/sandbox2/policybuilder.h +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -31,6 +31,7 @@ #include "absl/memory/memory.h" #include "absl/strings/string_view.h" #include "sandboxed_api/sandbox2/mounts.h" +#include "sandboxed_api/sandbox2/network_proxy/filtering.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/util/statusor.h" @@ -501,6 +502,10 @@ class PolicyBuilder final { // Not recommended PolicyBuilder& SetRootWritable(); + // Allows connections to this IP. + PolicyBuilder& AllowIPv4(const std::string& ip_and_mask, uint32_t port = 0); + PolicyBuilder& AllowIPv6(const std::string& ip_and_mask, uint32_t port = 0); + private: friend class PolicyBuilderPeer; // For testing friend class StackTracePeer; @@ -542,6 +547,9 @@ class PolicyBuilder final { // This function returns a PolicyBuilder so that we can use it in the status // macros PolicyBuilder& SetError(const sapi::Status& status); + + // Contains list of allowed hosts. + absl::optional allowed_hosts_; }; } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/result.cc b/sandboxed_api/sandbox2/result.cc index f359822..6415b5d 100644 --- a/sandboxed_api/sandbox2/result.cc +++ b/sandboxed_api/sandbox2/result.cc @@ -72,11 +72,15 @@ std::string Result::ToString() const { ReasonCodeEnumToString(static_cast(reason_code()))); break; case sandbox2::Result::VIOLATION: - result = absl::StrCat("SYSCALL VIOLATION - Violating Syscall ", - Syscall::GetArchDescription(GetSyscallArch()), "[", - reason_code(), "/", - Syscall(GetSyscallArch(), reason_code()).GetName(), - "] Stack: ", GetStackTrace()); + if (reason_code() == sandbox2::Result::VIOLATION_NETWORK) { + result = absl::StrCat("NETWORK VIOLATION: ", GetNetworkViolation()); + } else { + result = absl::StrCat( + "SYSCALL VIOLATION - Violating Syscall ", + Syscall::GetArchDescription(GetSyscallArch()), "[", reason_code(), + "/", Syscall(GetSyscallArch(), reason_code()).GetName(), + "] Stack: ", GetStackTrace()); + } break; case sandbox2::Result::SIGNALED: result = absl::StrCat("Process terminated with a SIGNAL - Signal: ", @@ -184,6 +188,8 @@ std::string Result::ReasonCodeEnumToString(ReasonCodeEnum value) { return "VIOLATION_SYSCALL"; case sandbox2::Result::VIOLATION_ARCH: return "VIOLATION_ARCH"; + case sandbox2::Result::VIOLATION_NETWORK: + return "VIOLATION_NETWORK"; } return absl::StrCat("UNKNOWN: ", value); } diff --git a/sandboxed_api/sandbox2/result.h b/sandboxed_api/sandbox2/result.h index 77cb043..498e8cf 100644 --- a/sandboxed_api/sandbox2/result.h +++ b/sandboxed_api/sandbox2/result.h @@ -85,6 +85,8 @@ class Result { // Codes used by status=`VIOLATION`: VIOLATION_SYSCALL, VIOLATION_ARCH, + VIOLATION_NETWORK = 0x10000000, // TODO(eternalred): temporary value, needs + // to be big until it's fixed }; Result() = default; @@ -119,6 +121,10 @@ class Result { syscall_ = std::move(syscall); } + void SetNetworkViolation(std::string network_violation) { + network_violation_ = std::move(network_violation); + } + StatusEnum final_status() const { return final_status_; } uintptr_t reason_code() const { return reason_code_; } @@ -137,6 +143,8 @@ class Result { const std::string& GetProgName() const { return prog_name_; } + const std::string& GetNetworkViolation() const { return network_violation_; } + void SetProgName(const std::string& name) { prog_name_ = name; } const std::string& GetProcMaps() const { return proc_maps_; } @@ -179,6 +187,8 @@ class Result { std::string prog_name_; // /proc/pid/maps of the main process. std::string proc_maps_; + // IP and port if network violation occurred + std::string network_violation_; // Final resource usage as defined in (man getrusage), for // the Monitor thread. rusage rusage_monitor_;