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
This commit is contained in:
bielec 2020-02-20 07:45:22 -08:00 committed by Copybara-Service
parent 5d81c822d8
commit 5a4e3f3d29
25 changed files with 829 additions and 118 deletions

View File

@ -89,7 +89,7 @@ cc_library(
":syscall", ":syscall",
":util", ":util",
"//sandboxed_api/util:status", "//sandboxed_api/util:status",
"@com_google_absl//absl/base", "//sandboxed_api/util:statusor",
"@com_google_absl//absl/memory", "@com_google_absl//absl/memory",
"@com_google_absl//absl/strings", "@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( cc_library(
name = "ipc", name = "ipc",
srcs = ["ipc.cc"], srcs = ["ipc.cc"],
@ -166,8 +136,6 @@ cc_library(
":comms", ":comms",
":logserver", ":logserver",
":logsink", ":logsink",
":network_proxy_client",
":network_proxy_server",
"@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory", "@com_google_absl//absl/memory",
"@com_google_absl//absl/strings", "@com_google_absl//absl/strings",
@ -186,6 +154,7 @@ cc_library(
":regs", ":regs",
":syscall", ":syscall",
":violation_cc_proto", ":violation_cc_proto",
"//sandboxed_api/sandbox2/network_proxy:filtering",
"//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/sandbox2/util:bpf_helper",
"//sandboxed_api/util:flags", "//sandboxed_api/util:flags",
"@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:core_headers",
@ -306,10 +275,10 @@ cc_library(
":client", ":client",
":executor", ":executor",
":comms", ":comms",
":violation_cc_proto",
":forkserver",
":forkserver_cc_proto", ":forkserver_cc_proto",
":global_forkserver", ":global_forkserver",
":violation_cc_proto",
":forkserver",
":ipc", ":ipc",
":limits", ":limits",
":logsink", ":logsink",
@ -321,7 +290,6 @@ cc_library(
":result", ":result",
":syscall", ":syscall",
":util", ":util",
":network_proxy_client",
"@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/container:flat_hash_set",
@ -333,11 +301,15 @@ cc_library(
"@com_google_absl//absl/time", "@com_google_absl//absl/time",
"@com_google_absl//absl/types:optional", "@com_google_absl//absl/types:optional",
"@org_kernel_libcap//:libcap", "@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",
"//sandboxed_api/sandbox2/unwind:unwind_cc_proto", "//sandboxed_api/sandbox2/unwind:unwind_cc_proto",
"//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/sandbox2/util:bpf_helper",
"//sandboxed_api/sandbox2/util:file_base", "//sandboxed_api/sandbox2/util:file_base",
"//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:fileops",
"//sandboxed_api/sandbox2/util:strerror",
"//sandboxed_api/util:raw_logging", "//sandboxed_api/util:raw_logging",
"//sandboxed_api/util:status", "//sandboxed_api/util:status",
"//sandboxed_api/util:statusor", "//sandboxed_api/util:statusor",
@ -364,7 +336,7 @@ cc_library(
deps = [ deps = [
":comms", ":comms",
":logsink", ":logsink",
":network_proxy_client", "//sandboxed_api/sandbox2/network_proxy:client",
"//sandboxed_api/sandbox2/util:file_helpers", "//sandboxed_api/sandbox2/util:file_helpers",
"//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:fileops",
"//sandboxed_api/sandbox2/util:strerror", "//sandboxed_api/sandbox2/util:strerror",

View File

@ -15,6 +15,7 @@
add_subdirectory(examples) add_subdirectory(examples)
add_subdirectory(unwind) add_subdirectory(unwind)
add_subdirectory(util) add_subdirectory(util)
add_subdirectory(network_proxy)
# sandboxed_api/sandbox2:bpfdisassembler # sandboxed_api/sandbox2:bpfdisassembler
add_library(sandbox2_bpfdisassembler STATIC add_library(sandbox2_bpfdisassembler STATIC
@ -121,36 +122,6 @@ target_link_libraries(sandbox2_logsink
PUBLIC glog::glog 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 # sandboxed_api/sandbox2:ipc
add_library(sandbox2_ipc STATIC add_library(sandbox2_ipc STATIC
ipc.cc ipc.cc

View File

@ -40,7 +40,6 @@
#include "absl/strings/str_join.h" #include "absl/strings/str_join.h"
#include "absl/strings/str_split.h" #include "absl/strings/str_split.h"
#include "sandboxed_api/sandbox2/comms.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/sanitizer.h"
#include "sandboxed_api/sandbox2/util/strerror.h" #include "sandboxed_api/sandbox2/util/strerror.h"
#include "sandboxed_api/util/raw_logging.h" #include "sandboxed_api/util/raw_logging.h"

View File

@ -25,7 +25,7 @@
#include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/logsink.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 { namespace sandbox2 {

View File

@ -14,10 +14,10 @@
# The 'network proxy' example demonstrates how to use network proxy server. # The 'network proxy' example demonstrates how to use network proxy server.
licenses(["notice"])
load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts") load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts")
licenses(["notice"])
# Executor # Executor
cc_binary( cc_binary(
name = "networkproxy_sandbox", name = "networkproxy_sandbox",
@ -27,6 +27,7 @@ cc_binary(
deps = [ deps = [
"//sandboxed_api/sandbox2", "//sandboxed_api/sandbox2",
"//sandboxed_api/sandbox2:comms", "//sandboxed_api/sandbox2:comms",
"//sandboxed_api/sandbox2/network_proxy:filtering",
"//sandboxed_api/sandbox2/util:bpf_helper", "//sandboxed_api/sandbox2/util:bpf_helper",
"//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:fileops",
"//sandboxed_api/sandbox2/util:runfiles", "//sandboxed_api/sandbox2/util:runfiles",
@ -42,7 +43,7 @@ cc_binary(
deps = [ deps = [
"//sandboxed_api/sandbox2:client", "//sandboxed_api/sandbox2:client",
"//sandboxed_api/sandbox2:comms", "//sandboxed_api/sandbox2:comms",
"//sandboxed_api/sandbox2:network_proxy_client", "//sandboxed_api/sandbox2/network_proxy:client",
"//sandboxed_api/sandbox2/util:fileops", "//sandboxed_api/sandbox2/util:fileops",
"@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/strings:str_format",
], ],

View File

@ -14,7 +14,7 @@
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "sandboxed_api/sandbox2/client.h" #include "sandboxed_api/sandbox2/client.h"
#include "sandboxed_api/sandbox2/comms.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" #include "sandboxed_api/sandbox2/util/fileops.h"
static ssize_t ReadFromFd(int fd, uint8_t* buf, size_t size) { 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())) { if (!CommunicationTest(client.get())) {
return 4; return 4;
} }
return 0; return 0;
} }

View File

@ -42,6 +42,8 @@ std::unique_ptr<sandbox2::Policy> GetPolicy(absl::string_view sandboxee_path) {
.AllowSyscall(__NR_munmap) .AllowSyscall(__NR_munmap)
.AllowSyscall(__NR_getpid) .AllowSyscall(__NR_getpid)
.AddNetworkProxyHandlerPolicy() .AddNetworkProxyHandlerPolicy()
.AllowTcMalloc()
.AllowIPv6("::1")
.AddLibrariesForBinary(sandboxee_path) .AddLibrariesForBinary(sandboxee_path)
.BuildOrDie(); .BuildOrDie();
} }
@ -129,10 +131,7 @@ int main(int argc, char** argv) {
// is capable of enabling sandboxing on its own). // is capable of enabling sandboxing on its own).
->set_enable_sandbox_before_exec(false) ->set_enable_sandbox_before_exec(false)
// Set cwd to / to get rids of warnings connected with file namespace. // Set cwd to / to get rids of warnings connected with file namespace.
.set_cwd("/") .set_cwd("/");
// Enable built-in network proxy.
.ipc()
->EnableNetworkProxyServer();
executor executor
->limits() ->limits()
// Remove restrictions on the size of address-space of sandboxed // Remove restrictions on the size of address-space of sandboxed

View File

@ -17,16 +17,12 @@
#include "sandboxed_api/sandbox2/ipc.h" #include "sandboxed_api/sandbox2/ipc.h"
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <thread>
#include <thread> // NOLINT(build/c++11)
#include <glog/logging.h> #include <glog/logging.h>
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "sandboxed_api/sandbox2/logserver.h" #include "sandboxed_api/sandbox2/logserver.h"
#include "sandboxed_api/sandbox2/logsink.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 { namespace sandbox2 {
@ -103,16 +99,4 @@ void IPC::EnableLogServer() {
log_thread.detach(); 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 } // namespace sandbox2

View File

@ -23,7 +23,6 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include "absl/base/macros.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/comms.h"
@ -60,10 +59,6 @@ class IPC final {
// Client::SendLogsToSupervisor in the sandboxee. // Client::SendLogsToSupervisor in the sandboxee.
void EnableLogServer(); 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: private:
friend class Executor; friend class Executor;
friend class Monitor; friend class Monitor;

View File

@ -53,6 +53,7 @@
#include "sandboxed_api/sandbox2/limits.h" #include "sandboxed_api/sandbox2/limits.h"
#include "sandboxed_api/sandbox2/mounts.h" #include "sandboxed_api/sandbox2/mounts.h"
#include "sandboxed_api/sandbox2/namespace.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/policy.h"
#include "sandboxed_api/sandbox2/regs.h" #include "sandboxed_api/sandbox2/regs.h"
#include "sandboxed_api/sandbox2/result.h" #include "sandboxed_api/sandbox2/result.h"
@ -140,6 +141,9 @@ Monitor::~Monitor() {
if (log_file_) { if (log_file_) {
std::fclose(log_file_); std::fclose(log_file_);
} }
if (network_proxy_server_) {
network_proxy_thread_.join();
}
} }
namespace { namespace {
@ -198,6 +202,10 @@ void Monitor::Run() {
// sandbox master/monitor, which ptrace_attach'es to the child. // sandbox master/monitor, which ptrace_attach'es to the child.
int clone_flags = CLONE_UNTRACED; int clone_flags = CLONE_UNTRACED;
if (policy_->allowed_hosts_) {
EnableNetworkProxyServer();
}
// Get PID of the sandboxee. // Get PID of the sandboxee.
pid_t init_pid = 0; pid_t init_pid = 0;
Namespace* ns = policy_->GetNamespace(); Namespace* ns = policy_->GetNamespace();
@ -349,6 +357,14 @@ void Monitor::MainLoop(sigset_t* sset) {
KillSandboxee(); 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 // It should be a non-blocking operation (hence WNOHANG), so this function
// returns quickly if there are no events to be processed. // returns quickly if there are no events to be processed.
// Prioritize main pid to avoid resource starvation // Prioritize main pid to avoid resource starvation
@ -401,7 +417,10 @@ void Monitor::MainLoop(sigset_t* sset) {
VLOG(1) << "PID: " << ret << " terminated with signal: " VLOG(1) << "PID: " << ret << " terminated with signal: "
<< util::GetSignalName(WTERMSIG(status)); << util::GetSignalName(WTERMSIG(status));
if (ret == pid_) { 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); SetExitStatusCode(Result::EXTERNAL_KILL, 0);
} else if (timed_out_) { } else if (timed_out_) {
SetExitStatusCode(Result::TIMEOUT, 0); SetExitStatusCode(Result::TIMEOUT, 0);
@ -794,7 +813,10 @@ void Monitor::EventPtraceExit(pid_t pid, int event_msg) {
// 3) Regular signal/other exit cause. // 3) Regular signal/other exit cause.
if (pid == pid_) { if (pid == pid_) {
VLOG(1) << "PID: " << pid << " main special exit"; 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); SetExitStatusCode(Result::EXTERNAL_KILL, 0);
} else if (timed_out_) { } else if (timed_out_) {
SetExitStatusCode(Result::TIMEOUT, 0); 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<NetworkProxyServer>(
fd, &policy_->allowed_hosts_.value(), pthread_self());
network_proxy_thread_ = std::thread(&NetworkProxyServer::Run,
network_proxy_server_.get());
}
} // namespace sandbox2 } // namespace sandbox2

View File

@ -26,11 +26,13 @@
#include <cstdio> #include <cstdio>
#include <ctime> #include <ctime>
#include <memory> #include <memory>
#include <thread>
#include "absl/synchronization/notification.h" #include "absl/synchronization/notification.h"
#include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/executor.h"
#include "sandboxed_api/sandbox2/ipc.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/notify.h"
#include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/regs.h" #include "sandboxed_api/sandbox2/regs.h"
@ -139,6 +141,10 @@ class Monitor final {
// Processes stop path. // Processes stop path.
void EventPtraceStop(pid_t pid, int stopsig); 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. // Internal objects, owned by the Sandbox2 object.
Executor* executor_; Executor* executor_;
Notify* notify_; Notify* notify_;
@ -168,6 +174,8 @@ class Monitor final {
std::atomic<int64_t> deadline_millis_{0}; std::atomic<int64_t> deadline_millis_{0};
// Was external kill sent to the sandboxee // Was external kill sent to the sandboxee
bool external_kill_ = false; bool external_kill_ = false;
// Network violation occurred and process of killing sandboxee started
bool network_violation_ = false;
// Is the sandboxee timed out // Is the sandboxee timed out
bool timed_out_ = false; bool timed_out_ = false;
// Should we dump the main sandboxed PID's stack? // Should we dump the main sandboxed PID's stack?
@ -178,6 +186,12 @@ class Monitor final {
// Log file specified by // Log file specified by
// --sandbox_danger_danger_permit_all_and_log flag. // --sandbox_danger_danger_permit_all_and_log flag.
FILE* log_file_ = nullptr; FILE* log_file_ = nullptr;
// Handle to the class responsible for proxying and validating connect()
// requests.
std::unique_ptr<NetworkProxyServer> network_proxy_server_;
std::thread network_proxy_thread_;
}; };
} // namespace sandbox2 } // namespace sandbox2

View File

@ -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",
],
)

View File

@ -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()

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "sandboxed_api/sandbox2/network_proxy_client.h" #include "sandboxed_api/sandbox2/network_proxy/client.h"
#include <linux/net.h> #include <linux/net.h>
#include <linux/seccomp.h> #include <linux/seccomp.h>

View File

@ -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 <arpa/inet.h>
#include <glog/logging.h>
#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<std::string> 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<std::string> 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<std::string> AddrToString(const struct sockaddr* saddr) {
switch (saddr->sa_family) {
case AF_INET:
return Addr4ToString(reinterpret_cast<const struct sockaddr_in*>(saddr));
case AF_INET6:
return Addr6ToString(reinterpret_cast<const struct sockaddr_in6*>(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<std::string> 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<uint32_t>(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<const struct sockaddr_in*>(saddr));
case AF_INET6:
return IsIPv6Allowed(reinterpret_cast<const struct sockaddr_in6*>(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

View File

@ -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 <netinet/in.h>
#include <memory>
#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<std::string> 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<IPv4> allowed_IPv4_;
std::vector<IPv6> allowed_IPv6_;
};
} // namespace sandbox2
#endif // SANDBOXED_API_SANDBOX2_NETWORK_PROXY_FILTERING_H_

View File

@ -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 <arpa/inet.h>
#include <linux/unistd.h>
#include <string.h>
#include <glog/logging.h>
#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<struct sockaddr*>(&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<struct sockaddr*>(&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

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "sandboxed_api/sandbox2/network_proxy_server.h" #include "sandboxed_api/sandbox2/network_proxy/server.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
@ -20,6 +20,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <syscall.h> #include <syscall.h>
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
@ -29,8 +30,13 @@
namespace sandbox2 { namespace sandbox2 {
NetworkProxyServer::NetworkProxyServer(int fd) NetworkProxyServer::NetworkProxyServer(int fd, AllowedHosts* allowed_hosts,
: comms_{absl::make_unique<Comms>(fd)}, fatal_error_{false} {} pthread_t monitor_thread_id)
: violation_occurred_(false),
comms_{absl::make_unique<Comms>(fd)},
fatal_error_(false),
monitor_thread_id_(monitor_thread_id),
allowed_hosts_(allowed_hosts) {}
void NetworkProxyServer::ProcessConnectRequest() { void NetworkProxyServer::ProcessConnectRequest() {
std::vector<uint8_t> addr; std::vector<uint8_t> addr;
@ -39,18 +45,22 @@ void NetworkProxyServer::ProcessConnectRequest() {
return; return;
} }
const struct sockaddr_in* saddr = const struct sockaddr* saddr = reinterpret_cast<const sockaddr*>(addr.data());
reinterpret_cast<const sockaddr_in*>(addr.data());
// Only IPv4 TCP and IPv6 TCP are supported. // 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) && (addr.size() == sizeof(sockaddr_in6) &&
saddr->sin_family == AF_INET6))) { saddr->sa_family == AF_INET6))) {
SendError(EINVAL); SendError(EINVAL);
return; 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) { if (new_socket < 0) {
SendError(errno); SendError(errno);
return; return;
@ -73,7 +83,8 @@ void NetworkProxyServer::ProcessConnectRequest() {
} }
void NetworkProxyServer::Run() { void NetworkProxyServer::Run() {
while (!fatal_error_) { while (!fatal_error_ &&
!violation_occurred_.load(std::memory_order_relaxed)) {
ProcessConnectRequest(); ProcessConnectRequest();
} }
LOG(INFO) LOG(INFO)
@ -92,4 +103,14 @@ void NetworkProxyServer::NotifySuccess() {
} }
} }
void NetworkProxyServer::NotifyViolation(const struct sockaddr* saddr) {
sapi::StatusOr<std::string> 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 } // namespace sandbox2

View File

@ -18,15 +18,18 @@
#include <memory> #include <memory>
#include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/network_proxy/filtering.h"
namespace sandbox2 { namespace sandbox2 {
// This is a proxy server that spawns connected sockets on requests. // 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 // 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 { class NetworkProxyServer {
public: public:
explicit NetworkProxyServer(int fd); NetworkProxyServer(int fd, AllowedHosts* allowed_hosts,
pthread_t monitor_thread_id);
NetworkProxyServer(const NetworkProxyServer&) = delete; NetworkProxyServer(const NetworkProxyServer&) = delete;
NetworkProxyServer& operator=(const NetworkProxyServer&) = delete; NetworkProxyServer& operator=(const NetworkProxyServer&) = delete;
@ -34,6 +37,11 @@ class NetworkProxyServer {
// Starts handling incoming connection requests. // Starts handling incoming connection requests.
void Run(); void Run();
// When the network rules were violated violation_occurred_ is set and
// violation_msg_ contains details about the host.
std::atomic<bool> violation_occurred_;
std::string violation_msg_;
private: private:
// Notifies the network proxy client about the error and sends its code. // Notifies the network proxy client about the error and sends its code.
void SendError(int saved_errno); void SendError(int saved_errno);
@ -44,8 +52,15 @@ class NetworkProxyServer {
// Serves connection requests from the network proxy client. // Serves connection requests from the network proxy client.
void ProcessConnectRequest(); void ProcessConnectRequest();
// Throw a violation when the network rules are subverted.
void NotifyViolation(const struct sockaddr* saddr);
std::unique_ptr<Comms> comms_; std::unique_ptr<Comms> comms_;
bool fatal_error_; bool fatal_error_;
pthread_t monitor_thread_id_;
// Contains list of allowed to connect hosts.
AllowedHosts* allowed_hosts_;
}; };
} // namespace sandbox2 } // namespace sandbox2

View File

@ -31,6 +31,7 @@
#include "absl/base/macros.h" #include "absl/base/macros.h"
#include "absl/types/optional.h" #include "absl/types/optional.h"
#include "sandboxed_api/sandbox2/namespace.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/syscall.h"
#include "sandboxed_api/sandbox2/violation.pb.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. // Get a policy which would allow the Monitor module to track all syscalls.
std::vector<sock_filter> GetTrackingPolicy() const; std::vector<sock_filter> GetTrackingPolicy() const;
// Contains a list of hosts the sandboxee is allowed to connect to.
absl::optional<AllowedHosts> allowed_hosts_;
friend class Monitor; friend class Monitor;
friend class PolicyBuilder; friend class PolicyBuilder;
friend class PolicyBuilderPeer; // For testing friend class PolicyBuilderPeer; // For testing

View File

@ -699,6 +699,7 @@ sapi::StatusOr<std::unique_ptr<Policy>> PolicyBuilder::TryBuild() {
StoreDescription(pb_description.get()); StoreDescription(pb_description.get());
output->policy_builder_description_ = std::move(pb_description); output->policy_builder_description_ = std::move(pb_description);
output->allowed_hosts_ = std::move(allowed_hosts_);
already_built_ = true; already_built_ = true;
return std::move(output); return std::move(output);
} }
@ -847,6 +848,15 @@ PolicyBuilder& PolicyBuilder::CollectStacktracesOnKill(bool enable) {
} }
PolicyBuilder& PolicyBuilder::AddNetworkProxyPolicy() { 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_WAKE);
AllowFutexOp(FUTEX_WAIT); AllowFutexOp(FUTEX_WAIT);
AllowFutexOp(FUTEX_WAIT_BITSET); 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 } // namespace sandbox2

View File

@ -31,6 +31,7 @@
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "sandboxed_api/sandbox2/mounts.h" #include "sandboxed_api/sandbox2/mounts.h"
#include "sandboxed_api/sandbox2/network_proxy/filtering.h"
#include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/util/statusor.h" #include "sandboxed_api/util/statusor.h"
@ -501,6 +502,10 @@ class PolicyBuilder final {
// Not recommended // Not recommended
PolicyBuilder& SetRootWritable(); 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: private:
friend class PolicyBuilderPeer; // For testing friend class PolicyBuilderPeer; // For testing
friend class StackTracePeer; friend class StackTracePeer;
@ -542,6 +547,9 @@ class PolicyBuilder final {
// This function returns a PolicyBuilder so that we can use it in the status // This function returns a PolicyBuilder so that we can use it in the status
// macros // macros
PolicyBuilder& SetError(const sapi::Status& status); PolicyBuilder& SetError(const sapi::Status& status);
// Contains list of allowed hosts.
absl::optional<AllowedHosts> allowed_hosts_;
}; };
} // namespace sandbox2 } // namespace sandbox2

View File

@ -72,11 +72,15 @@ std::string Result::ToString() const {
ReasonCodeEnumToString(static_cast<ReasonCodeEnum>(reason_code()))); ReasonCodeEnumToString(static_cast<ReasonCodeEnum>(reason_code())));
break; break;
case sandbox2::Result::VIOLATION: case sandbox2::Result::VIOLATION:
result = absl::StrCat("SYSCALL VIOLATION - Violating Syscall ", if (reason_code() == sandbox2::Result::VIOLATION_NETWORK) {
Syscall::GetArchDescription(GetSyscallArch()), "[", result = absl::StrCat("NETWORK VIOLATION: ", GetNetworkViolation());
reason_code(), "/", } else {
Syscall(GetSyscallArch(), reason_code()).GetName(), result = absl::StrCat(
"SYSCALL VIOLATION - Violating Syscall ",
Syscall::GetArchDescription(GetSyscallArch()), "[", reason_code(),
"/", Syscall(GetSyscallArch(), reason_code()).GetName(),
"] Stack: ", GetStackTrace()); "] Stack: ", GetStackTrace());
}
break; break;
case sandbox2::Result::SIGNALED: case sandbox2::Result::SIGNALED:
result = absl::StrCat("Process terminated with a SIGNAL - Signal: ", result = absl::StrCat("Process terminated with a SIGNAL - Signal: ",
@ -184,6 +188,8 @@ std::string Result::ReasonCodeEnumToString(ReasonCodeEnum value) {
return "VIOLATION_SYSCALL"; return "VIOLATION_SYSCALL";
case sandbox2::Result::VIOLATION_ARCH: case sandbox2::Result::VIOLATION_ARCH:
return "VIOLATION_ARCH"; return "VIOLATION_ARCH";
case sandbox2::Result::VIOLATION_NETWORK:
return "VIOLATION_NETWORK";
} }
return absl::StrCat("UNKNOWN: ", value); return absl::StrCat("UNKNOWN: ", value);
} }

View File

@ -85,6 +85,8 @@ class Result {
// Codes used by status=`VIOLATION`: // Codes used by status=`VIOLATION`:
VIOLATION_SYSCALL, VIOLATION_SYSCALL,
VIOLATION_ARCH, VIOLATION_ARCH,
VIOLATION_NETWORK = 0x10000000, // TODO(eternalred): temporary value, needs
// to be big until it's fixed
}; };
Result() = default; Result() = default;
@ -119,6 +121,10 @@ class Result {
syscall_ = std::move(syscall); syscall_ = std::move(syscall);
} }
void SetNetworkViolation(std::string network_violation) {
network_violation_ = std::move(network_violation);
}
StatusEnum final_status() const { return final_status_; } StatusEnum final_status() const { return final_status_; }
uintptr_t reason_code() const { return reason_code_; } 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& GetProgName() const { return prog_name_; }
const std::string& GetNetworkViolation() const { return network_violation_; }
void SetProgName(const std::string& name) { prog_name_ = name; } void SetProgName(const std::string& name) { prog_name_ = name; }
const std::string& GetProcMaps() const { return proc_maps_; } const std::string& GetProcMaps() const { return proc_maps_; }
@ -179,6 +187,8 @@ class Result {
std::string prog_name_; std::string prog_name_;
// /proc/pid/maps of the main process. // /proc/pid/maps of the main process.
std::string proc_maps_; std::string proc_maps_;
// IP and port if network violation occurred
std::string network_violation_;
// Final resource usage as defined in <sys/resource.h> (man getrusage), for // Final resource usage as defined in <sys/resource.h> (man getrusage), for
// the Monitor thread. // the Monitor thread.
rusage rusage_monitor_; rusage rusage_monitor_;