Now the network proxy client can automatically redirect connect syscalls to a handler that will send the data (syscall arguments) to the proxy server automatically and will return the obtained socket from the proxy server, in the future rules like allowed IP, protocols, etc. will be added

PiperOrigin-RevId: 259512665
Change-Id: I2747c7548ab24c7d2c90abb303fd783c11fed6f4
This commit is contained in:
bielec 2019-07-23 04:40:47 -07:00 committed by Copybara-Service
parent ae9836e6bf
commit ef7592cfdd
18 changed files with 1091 additions and 46 deletions

View File

@ -148,6 +148,7 @@ cc_library(
":comms",
"//sandboxed_api/sandbox2/util:strerror",
"//sandboxed_api/util:status",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
"@com_google_glog//:glog",
@ -366,6 +367,7 @@ cc_library(
"//sandboxed_api/sandbox2/util:fileops",
"//sandboxed_api/sandbox2/util:strerror",
"//sandboxed_api/util:raw_logging",
"//sandboxed_api/util:status",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",

View File

@ -40,9 +40,11 @@
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/network_proxy_client.h"
#include "sandboxed_api/sandbox2/sanitizer.h"
#include "sandboxed_api/sandbox2/util/strerror.h"
#include "sandboxed_api/util/raw_logging.h"
#include "sandboxed_api/util/canonical_errors.h"
namespace sandbox2 {
@ -256,4 +258,15 @@ NetworkProxyClient* Client::GetNetworkProxyClient() {
return proxy_client_.get();
}
sapi::Status Client::InstallNetworkProxyHandler() {
if (fd_map_.find(NetworkProxyClient::kFDName) == fd_map_.end()) {
return sapi::FailedPreconditionError(
"InstallNetworkProxyHandler() must be called at most once after the "
"sandbox is installed. Also, the NetworkProxyServer needs to be "
"enabled.");
}
return NetworkProxyHandler::InstallNetworkProxyHandler(
GetNetworkProxyClient());
}
} // namespace sandbox2

View File

@ -58,6 +58,10 @@ class Client {
// for the first time.
NetworkProxyClient* GetNetworkProxyClient();
// Redirects the connect() syscall to the ConnectHandler() method in
// the NetworkProxyClient class.
sapi::Status InstallNetworkProxyHandler();
protected:
// Comms used for synchronization with the monitor, not owned by the object.
Comms* comms_;

View File

@ -15,6 +15,8 @@
if(SAPI_ENABLE_EXAMPLES)
add_subdirectory(crc4)
add_subdirectory(custom_fork)
add_subdirectory(network)
add_subdirectory(network_proxy)
add_subdirectory(static)
add_subdirectory(tool)
add_subdirectory(zlib)

View File

@ -0,0 +1,52 @@
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The 'network' example demonstrates:
# - separate executor and sandboxee
# - sandboxee enables sandboxing by calling SandboxMeHere()
# - strict syscall policy
# - sandbox2::Comms for data exchange (IPC)
licenses(["notice"]) # Apache 2.0
load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts")
# Executor
cc_binary(
name = "network_sandbox",
srcs = ["network_sandbox.cc"],
copts = sapi_platform_copts(),
data = [":network_bin"],
deps = [
"//sandboxed_api/sandbox2",
"//sandboxed_api/sandbox2:comms",
"//sandboxed_api/sandbox2/util:bpf_helper",
"//sandboxed_api/sandbox2/util:fileops",
"//sandboxed_api/sandbox2/util:runfiles",
"//sandboxed_api/util:flags",
],
)
# Sandboxee
cc_binary(
name = "network_bin",
srcs = ["network_bin.cc"],
copts = sapi_platform_copts(),
deps = [
"//sandboxed_api/sandbox2:client",
"//sandboxed_api/sandbox2:comms",
"//sandboxed_api/sandbox2:util",
"@com_google_absl//absl/strings:str_format",
],
)

View File

@ -0,0 +1,44 @@
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# sandboxed_api/sandbox2/examples/network:network_sandbox
add_executable(sandbox2_network_sandbox
network_sandbox.cc
)
add_executable(sandbox2::network_sandbox ALIAS sandbox2_network_sandbox)
add_dependencies(sandbox2_network_sandbox
sandbox2::network_bin
)
target_link_libraries(sandbox2_network_sandbox PRIVATE
glog::glog
sandbox2::bpf_helper
sandbox2::comms
sandbox2::fileops
sandbox2::runfiles
sandbox2::sandbox2
sapi::base
)
# sandboxed_api/sandbox2/examples/network_bin:network_bin
add_executable(network_bin
network_bin.cc
)
add_executable(sandbox2::network_bin ALIAS network_bin)
target_link_libraries(network_bin PRIVATE
absl::str_format
sandbox2::client
sandbox2::comms
sandbox2::fileops
sapi::base
)

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.
// This file is an example of a network sandboxed binary inside a network
// namespace. It can't connect with the server directly, but the executor can
// establish a connection and pass the connected socket to the sandboxee.
#include <sys/socket.h>
#include <syscall.h>
#include <cstring>
#include <string>
#include "absl/strings/str_format.h"
#include "sandboxed_api/sandbox2/client.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/util.h"
static ssize_t ReadFromFd(int fd, uint8_t* buf, size_t size) {
ssize_t received = 0;
while (received < size) {
ssize_t read_status =
TEMP_FAILURE_RETRY(read(fd, &buf[received], size - received));
if (read_status == 0) {
break;
}
if (read_status < 0) {
return -1;
}
received += read_status;
}
return received;
}
static bool CommunicationTest(int sock) {
char received[1025] = {0};
if (ReadFromFd(sock, reinterpret_cast<uint8_t*>(received),
sizeof(received) - 1) <= 0) {
LOG(ERROR) << "Data receiving error";
return false;
}
absl::PrintF("Sandboxee received data from the server:\n\n%s\n", received);
if (strcmp(received, "Hello World\n")) {
LOG(ERROR) << "Data receiving error";
return false;
}
return true;
}
int main(int argc, char** argv) {
// Set-up the sandbox2::Client object, using a file descriptor (1023).
sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD);
sandbox2::Client sandbox2_client(&comms);
// Enable sandboxing from here.
sandbox2_client.SandboxMeHere();
int client;
if (!comms.RecvFD(&client)) {
fputs("sandboxee: !comms.RecvFD(&client) failed\n", stderr);
return 1;
}
if (!CommunicationTest(client)) return 2;
return 0;
}

View File

@ -0,0 +1,237 @@
// Copyright 2019 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// A demo sandbox for the network binary.
#include <arpa/inet.h>
#include <linux/filter.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <syscall.h>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <glog/logging.h>
#include "sandboxed_api/util/flag.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/executor.h"
#include "sandboxed_api/sandbox2/ipc.h"
#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/sandbox2.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/runfiles.h"
namespace {
std::unique_ptr<sandbox2::Policy> GetPolicy(absl::string_view sandboxee_path) {
return sandbox2::PolicyBuilder()
.EnableNamespaces()
.AllowExit()
.AllowMmap()
.AllowRead()
.AllowWrite()
.AllowSyscall(__NR_close)
.AllowSyscall(__NR_recvmsg) // RecvFD
.AllowSyscall(__NR_sendto) // send
.AllowStat() // printf,puts
.AddLibrariesForBinary(sandboxee_path)
.BuildOrDie();
}
void Server(int port) {
sandbox2::file_util::fileops::FDCloser s{
socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0)};
if (s.get() < 0) {
PLOG(ERROR) << "socket() failed";
return;
}
{
int enable = 1;
if (setsockopt(s.get(), SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) <
0) {
PLOG(ERROR) << "setsockopt(SO_REUSEADDR) failed";
return;
}
}
// Listen to localhost only.
struct sockaddr_in6 addr = {};
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);
int err = inet_pton(AF_INET6, "::1", &addr.sin6_addr.s6_addr);
if (err == 0) {
LOG(ERROR) << "inet_pton() failed";
return;
} else if (err == -1) {
PLOG(ERROR) << "inet_pton() failed";
return;
}
if (bind(s.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) {
PLOG(ERROR) << "bind() failed";
return;
}
if (listen(s.get(), 1) < 0) {
PLOG(ERROR) << "listen() failed";
return;
}
sandbox2::file_util::fileops::FDCloser client{accept(s.get(), 0, 0)};
if (client.get() < 0) {
PLOG(ERROR) << "accept() failed";
return;
}
const char msg[] = "Hello World\n";
write(client.get(), msg, strlen(msg));
}
int ConnectToServer(int port) {
int s = socket(AF_INET6, SOCK_STREAM, 0);
if (s < 0) {
PLOG(ERROR) << "socket() failed";
return -1;
}
struct sockaddr_in6 saddr {};
saddr.sin6_family = AF_INET6;
saddr.sin6_port = htons(port);
int err = inet_pton(AF_INET6, "::1", &saddr.sin6_addr);
if (err == 0) {
LOG(ERROR) << "inet_pton() failed";
close(s);
return -1;
} else if (err == -1) {
PLOG(ERROR) << "inet_pton() failed";
close(s);
return -1;
}
err = connect(s, reinterpret_cast<const struct sockaddr*>(&saddr),
sizeof(saddr));
if (err != 0) {
LOG(ERROR) << "connect() failed";
close(s);
return -1;
}
LOG(INFO) << "Connected to the server";
return s;
}
bool HandleSandboxee(sandbox2::Comms* comms, int port) {
// connect to the server and pass the file descriptor to sandboxee.
int client = ConnectToServer(port);
if (client <= 0) {
LOG(ERROR) << "connect_to_server() failed";
return false;
}
if (!comms->SendFD(client)) {
LOG(ERROR) << "sandboxee_comms->SendFD(client) failed";
close(client);
return false;
}
close(client);
return true;
}
} // namespace
int main(int argc, char** argv) {
// This test is incompatible with sanitizers.
// The `SKIP_SANITIZERS_AND_COVERAGE` macro won't work for us here since we
// need to return something.
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(THREAD_SANITIZER)
return EXIT_SUCCESS;
#endif
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
int port = 8085;
std::thread server_thread{Server,port};
server_thread.detach();
std::string path = sandbox2::GetInternalDataDependencyFilePath(
"sandbox2/examples/network/network_bin");
std::vector<std::string> args = {path};
std::vector<std::string> envs = {};
auto executor = absl::make_unique<sandbox2::Executor>(path, args, envs);
executor
// Sandboxing is enabled by the binary itself (i.e. the crc4bin is capable
// of enabling sandboxing on its own).
->set_enable_sandbox_before_exec(false)
// Set cwd to / to get rids of warnings connected with file namespace.
.set_cwd("/");
executor
->limits()
// Remove restrictions on the size of address-space of sandboxed
// processes.
->set_rlimit_as(RLIM64_INFINITY)
// Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
// these many bytes to the file-system.
.set_rlimit_fsize(10000)
.set_rlimit_cpu(100) // The CPU time limit in seconds
.set_walltime_limit(absl::Seconds(100));
auto policy = GetPolicy(path);
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
auto* comms = s2.comms();
// Let the sandboxee run.
if (!s2.RunAsync()) {
auto result = s2.AwaitResult();
LOG(ERROR) << "RunAsync failed: " << result.ToString();
return 2;
}
if (!HandleSandboxee(comms, port)) {
if (!s2.IsTerminated()) {
// Kill the sandboxee, because failure to receive the data over the Comms
// channel doesn't automatically mean that the sandboxee itself had
// already finished. The final reason will not be overwritten, so if
// sandboxee finished because of e.g. timeout, the TIMEOUT reason will be
// reported.
LOG(INFO) << "Killing sandboxee";
s2.Kill();
}
}
auto result = s2.AwaitResult();
if (result.final_status() != sandbox2::Result::OK) {
LOG(ERROR) << "Sandbox error: " << result.ToString();
return 3; // e.g. sandbox violation, signal (sigsegv).
}
auto code = result.reason_code();
if (code) {
LOG(ERROR) << "Sandboxee exited with non-zero: " << code;
return 4; // e.g. normal child error.
}
LOG(INFO) << "Sandboxee finished: " << result.ToString();
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,49 @@
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# The 'network proxy' example demonstrates how to use network proxy server.
licenses(["notice"]) # Apache 2.0
load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts")
# Executor
cc_binary(
name = "networkproxy_sandbox",
srcs = ["networkproxy_sandbox.cc"],
copts = sapi_platform_copts(),
data = [":networkproxy_bin"],
deps = [
"//sandboxed_api/sandbox2",
"//sandboxed_api/sandbox2:comms",
"//sandboxed_api/sandbox2/util:bpf_helper",
"//sandboxed_api/sandbox2/util:fileops",
"//sandboxed_api/sandbox2/util:runfiles",
"//sandboxed_api/util:flags",
],
)
# Sandboxee
cc_binary(
name = "networkproxy_bin",
srcs = ["networkproxy_bin.cc"],
copts = sapi_platform_copts(),
deps = [
"//sandboxed_api/sandbox2:client",
"//sandboxed_api/sandbox2:comms",
"//sandboxed_api/sandbox2:network_proxy_client",
"//sandboxed_api/sandbox2/util:fileops",
"@com_google_absl//absl/strings:str_format",
],
)

View File

@ -0,0 +1,45 @@
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# sandboxed_api/sandbox2/examples/network_proxy:networkproxy_sandbox
add_executable(sandbox2_networkproxy_sandbox
networkproxy_sandbox.cc
)
add_executable(sandbox2::networkproxy_sandbox ALIAS sandbox2_networkproxy_sandbox)
add_dependencies(sandbox2_networkproxy_sandbox
sandbox2::networkproxy_bin
)
target_link_libraries(sandbox2_networkproxy_sandbox PRIVATE
glog::glog
sandbox2::bpf_helper
sandbox2::comms
sandbox2::fileops
sandbox2::runfiles
sandbox2::sandbox2
sapi::base
)
# sandboxed_api/sandbox2/examples/networkproxy_bin:networkproxy_bin
add_executable(networkproxy_bin
networkproxy_bin.cc
)
add_executable(sandbox2::networkproxy_bin ALIAS networkproxy_bin)
target_link_libraries(networkproxy_bin PRIVATE
absl::str_format
sandbox2::client
sandbox2::comms
sandbox2::fileops
sandbox2::network_proxy_client
sapi::base
)

View File

@ -0,0 +1,117 @@
// This file is an example of a network sandboxed binary inside a network
// namespace. It can't connect with the server directly, but the executor can
// establish a connection and pass the connected socket to the sandboxee.
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syscall.h>
#include <cstring>
#include "absl/strings/str_format.h"
#include "sandboxed_api/sandbox2/client.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/network_proxy_client.h"
#include "sandboxed_api/sandbox2/util/fileops.h"
static ssize_t ReadFromFd(int fd, uint8_t* buf, size_t size) {
ssize_t received = 0;
while (received < size) {
ssize_t read_status =
TEMP_FAILURE_RETRY(read(fd, &buf[received], size - received));
if (read_status == 0) {
break;
}
if (read_status < 0) {
return -1;
}
received += read_status;
}
return received;
}
static bool CommunicationTest(int sock) {
char received[1025] = {0};
if (ReadFromFd(sock, reinterpret_cast<uint8_t*>(received),
sizeof(received) - 1) <= 0) {
LOG(ERROR) << "Data receiving error";
return false;
}
absl::PrintF("Sandboxee received data from the server:\n\n%s\n", received);
if (strcmp(received, "Hello World\n")) {
LOG(ERROR) << "Data receiving error";
return false;
}
return true;
}
static int ConnectToServer(int port) {
int s = socket(AF_INET6, SOCK_STREAM, 0);
if (s < 0) {
PLOG(ERROR) << "socket() failed";
return -1;
}
struct sockaddr_in6 saddr {};
saddr.sin6_family = AF_INET6;
saddr.sin6_port = htons(port);
int err = inet_pton(AF_INET6, "::1", &saddr.sin6_addr);
if (err == 0) {
LOG(ERROR) << "inet_pton() failed";
close(s);
return -1;
} else if (err == -1) {
PLOG(ERROR) << "inet_pton() failed";
close(s);
return -1;
}
err = connect(s, reinterpret_cast<const struct sockaddr*>(&saddr),
sizeof(saddr));
if (err != 0) {
LOG(ERROR) << "connect() failed";
close(s);
return -1;
}
LOG(INFO) << "Connected to the server";
return s;
}
int main(int argc, char** argv) {
// Set-up the sandbox2::Client object, using a file descriptor (1023).
sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD);
sandbox2::Client sandbox2_client(&comms);
// Enable sandboxing from here.
sandbox2_client.SandboxMeHere();
sapi::Status status = sandbox2_client.InstallNetworkProxyHandler();
if (!status.ok()) {
LOG(ERROR) << "InstallNetworkProxyHandler() failed: " << status.message();
return 1;
}
// Receive port number of the server
int port;
if (!comms.RecvInt32(&port)) {
LOG(ERROR) << "sandboxee_comms->RecvUint32(&crc4) failed";
return 2;
}
sandbox2::file_util::fileops::FDCloser client{ConnectToServer(port)};
if (client.get() == -1) {
return 3;
}
if (!CommunicationTest(client.get())) {
return 4;
}
return 0;
}

View File

@ -0,0 +1,176 @@
// A demo sandbox for the network binary.
#include <arpa/inet.h>
#include <linux/filter.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <syscall.h>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
#include <glog/logging.h>
#include "sandboxed_api/util/flag.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/executor.h"
#include "sandboxed_api/sandbox2/ipc.h"
#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/sandbox2.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
#include "sandboxed_api/sandbox2/util/fileops.h"
#include "sandboxed_api/sandbox2/util/runfiles.h"
namespace {
std::unique_ptr<sandbox2::Policy> GetPolicy(absl::string_view sandboxee_path) {
return sandbox2::PolicyBuilder()
.AllowExit()
.EnableNamespaces()
.AllowMmap()
.AllowRead()
.AllowWrite()
.AllowStat() // printf, puts
.AllowOpen()
.AllowSyscall(__NR_sendto) // send
.AllowSyscall(__NR_lseek)
.AllowSyscall(__NR_munmap)
.AllowSyscall(__NR_getpid)
.AddNetworkProxyHandlerPolicy()
.AddLibrariesForBinary(sandboxee_path)
.BuildOrDie();
}
void Server(int port) {
sandbox2::file_util::fileops::FDCloser s{
socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0)};
if (s.get() < 0) {
PLOG(ERROR) << "socket() failed";
return;
}
{
int enable = 1;
if (setsockopt(s.get(), SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) <
0) {
PLOG(ERROR) << "setsockopt(SO_REUSEADDR) failed";
return;
}
}
// Listen to localhost only.
struct sockaddr_in6 addr = {};
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);
int err = inet_pton(AF_INET6, "::1", &addr.sin6_addr.s6_addr);
if (err == 0) {
LOG(ERROR) << "inet_pton() failed";
return;
} else if (err == -1) {
PLOG(ERROR) << "inet_pton() failed";
return;
}
if (bind(s.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) {
PLOG(ERROR) << "bind() failed";
return;
}
if (listen(s.get(), 1) < 0) {
PLOG(ERROR) << "listen() failed";
return;
}
sandbox2::file_util::fileops::FDCloser client{accept(s.get(), 0, 0)};
if (client.get() < 0) {
PLOG(ERROR) << "accept() failed";
return;
}
const char kMsg[] = "Hello World\n";
write(client.get(), kMsg, ABSL_ARRAYSIZE(kMsg));
}
} // namespace
int main(int argc, char** argv) {
// This test is incompatible with sanitizers.
// The `SKIP_SANITIZERS_AND_COVERAGE` macro won't work for us here since we
// need to return something.
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(THREAD_SANITIZER)
return EXIT_SUCCESS;
#endif
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
int port = 8085;
std::thread server_thread{Server,port};
server_thread.detach();
std::string path = sandbox2::GetInternalDataDependencyFilePath(
"sandbox2/examples/network_proxy/networkproxy_bin");
std::vector<std::string> args = {path};
std::vector<std::string> envs = {};
auto executor = absl::make_unique<sandbox2::Executor>(path, args, envs);
executor
// Sandboxing is enabled by the binary itself (i.e. the networkproxy_bin
// is capable of enabling sandboxing on its own).
->set_enable_sandbox_before_exec(false)
// Set cwd to / to get rids of warnings connected with file namespace.
.set_cwd("/")
// Enable built-in network proxy.
.ipc()
->EnableNetworkProxyServer();
executor
->limits()
// Remove restrictions on the size of address-space of sandboxed
// processes.
->set_rlimit_as(RLIM64_INFINITY)
// Kill sandboxed processes with a signal (SIGXFSZ) if it writes more than
// these many bytes to the file-system.
.set_rlimit_fsize(10000)
// The CPU time limit in seconds.
.set_rlimit_cpu(100)
.set_walltime_limit(absl::Seconds(100));
auto policy = GetPolicy(path);
sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
auto* comms = s2.comms();
// Let the sandboxee run.
if (!s2.RunAsync()) {
auto result = s2.AwaitResult();
LOG(ERROR) << "RunAsync failed: " << result.ToString();
return 2;
}
// Send the port number via comms
if (!comms->SendInt32(port)) {
LOG(ERROR) << "sandboxee_comms->SendInt32() failed";
return 3;
}
auto result = s2.AwaitResult();
if (result.final_status() != sandbox2::Result::OK) {
LOG(ERROR) << "Sandbox error: " << result.ToString();
return 4; // e.g. sandbox violation, signal (sigsegv)
}
auto code = result.reason_code();
if (code) {
LOG(ERROR) << "Sandboxee exited with non-zero: " << code;
return 5; // e.g. normal child error
}
LOG(INFO) << "Sandboxee finished: " << result.ToString();
return EXIT_SUCCESS;
}

View File

@ -14,10 +14,17 @@
#include "sandboxed_api/sandbox2/network_proxy_client.h"
#include <linux/net.h>
#include <linux/seccomp.h>
#include <stdio.h>
#include <syscall.h>
#include <ucontext.h>
#include <cerrno>
#include <iostream>
#include <glog/logging.h>
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "sandboxed_api/sandbox2/util/strerror.h"
#include "sandboxed_api/util/canonical_errors.h"
@ -26,6 +33,25 @@
namespace sandbox2 {
#ifndef SYS_SECCOMP
constexpr int SYS_SECCOMP = 1;
#endif
#if defined(__x86_64__)
constexpr int kRegResult = REG_RAX;
constexpr int kRegSyscall = REG_RAX;
constexpr int kRegArg0 = REG_RDI;
constexpr int kRegArg1 = REG_RSI;
constexpr int kRegArg2 = REG_RDX;
#endif
#if defined(__powerpc64__)
constexpr int kRegResult = 3;
constexpr int kRegSyscall = 0;
constexpr int kRegArg0 = 3;
constexpr int kRegArg1 = 4;
constexpr int kRegArg2 = 5;
#endif
constexpr char NetworkProxyClient::kFDName[];
int NetworkProxyClient::ConnectHandler(int sockfd, const struct sockaddr* addr,
@ -34,7 +60,7 @@ int NetworkProxyClient::ConnectHandler(int sockfd, const struct sockaddr* addr,
if (status.ok()) {
return 0;
}
PLOG(ERROR) << "ConnectHandler() failed: " << status.message();
LOG(ERROR) << "ConnectHandler() failed: " << status.message();
return -1;
}
@ -86,9 +112,106 @@ sapi::Status NetworkProxyClient::ReceiveRemoteResult() {
if (result != 0) {
errno = result;
return sapi::InternalError(
absl::StrCat("Error in network proxy: ", StrError(errno)));
absl::StrCat("Error in network proxy server: ", StrError(errno)));
}
return sapi::OkStatus();
}
namespace {
static NetworkProxyHandler* g_network_proxy_handler = nullptr;
void SignalHandler(int nr, siginfo_t* info, void* void_context) {
g_network_proxy_handler->ProcessSeccompTrap(nr, info, void_context);
}
} // namespace
sapi::Status NetworkProxyHandler::InstallNetworkProxyHandler(
NetworkProxyClient* npc) {
if (g_network_proxy_handler) {
return sapi::AlreadyExistsError(
"Network proxy handler is already installed");
}
g_network_proxy_handler = new NetworkProxyHandler(npc);
return sapi::OkStatus();
}
void NetworkProxyHandler::InvokeOldAct(int nr, siginfo_t* info,
void* void_context) {
if (oldact_.sa_flags & SA_SIGINFO) {
if (oldact_.sa_sigaction) {
oldact_.sa_sigaction(nr, info, void_context);
}
} else if (oldact_.sa_handler == SIG_IGN) {
return;
} else if (oldact_.sa_handler == SIG_DFL) {
sigaction(SIGSYS, &oldact_, nullptr);
raise(SIGSYS);
} else if (oldact_.sa_handler) {
oldact_.sa_handler(nr);
}
} // namespace sandbox2
void NetworkProxyHandler::ProcessSeccompTrap(int nr, siginfo_t* info,
void* void_context) {
ucontext_t* ctx = (ucontext_t*)(void_context);
if (info->si_code != SYS_SECCOMP) {
InvokeOldAct(nr, info, void_context);
return;
}
if (!ctx) return;
#if defined(__x86_64__)
auto* registers = ctx->uc_mcontext.gregs;
#elif defined(__powerpc64__)
auto* registers = ctx->uc_mcontext.gp_regs;
using ppc_gpreg_t = std::decay<decltype(registers[0])>::type;
#endif
int syscall = registers[kRegSyscall];
int sockfd;
const struct sockaddr* addr;
socklen_t addrlen;
if (syscall == __NR_connect) {
sockfd = static_cast<int>(registers[kRegArg0]);
addr = reinterpret_cast<const struct sockaddr*>(registers[kRegArg1]);
addrlen = static_cast<socklen_t>(registers[kRegArg2]);
#if defined(__powerpc64__)
} else if (syscall == __NR_socketcall &&
static_cast<int>(registers[kRegArg0]) == SYS_CONNECT) {
ppc_gpreg_t* args = reinterpret_cast<ppc_gpreg_t*>(registers[kRegArg1]);
sockfd = static_cast<int>(args[0]);
addr = reinterpret_cast<const struct sockaddr*>(args[1]);
addrlen = static_cast<socklen_t>(args[2]);
#endif
} else {
InvokeOldAct(nr, info, void_context);
return;
}
sapi::Status result = network_proxy_client_->Connect(sockfd, addr, addrlen);
if (result.ok()) {
registers[kRegResult] = 0;
} else {
registers[kRegResult] = -errno;
}
}
void NetworkProxyHandler::InstallSeccompTrap() {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGSYS);
struct sigaction act = {};
act.sa_sigaction = &SignalHandler;
act.sa_flags = SA_SIGINFO;
CHECK_EQ(sigaction(SIGSYS, &act, &oldact_), 0);
CHECK_EQ(sigprocmask(SIG_UNBLOCK, &mask, nullptr), 0);
}
} // namespace sandbox2

View File

@ -50,6 +50,26 @@ class NetworkProxyClient {
absl::Mutex mutex_;
};
class NetworkProxyHandler {
public:
// Installs the handler that redirects connect() syscalls to the trap
// function. This function exchange data with NetworkProxyServer that checks
// if this connection is allowed and sends the connected socket to us.
// In other words, this function just use NetworkProxyClient class.
static sapi::Status InstallNetworkProxyHandler(NetworkProxyClient* npc);
void ProcessSeccompTrap(int nr, siginfo_t* info, void* void_context);
private:
NetworkProxyHandler(NetworkProxyClient* npc) : network_proxy_client_(npc) {
InstallSeccompTrap();
}
void InvokeOldAct(int nr, siginfo_t* info, void* void_context);
void InstallSeccompTrap();
struct sigaction oldact_;
NetworkProxyClient* network_proxy_client_;
};
} // namespace sandbox2
#endif // SANDBOXED_API_SANDBOX2_NETWORK_PROXY_CLIENT_H_

View File

@ -39,32 +39,35 @@ void NetworkProxyServer::ProcessConnectRequest() {
return;
}
// Only sockaddr_in is supported.
if (addr.size() != sizeof(sockaddr_in)) {
SendResult(-1, EINVAL);
return;
}
const struct sockaddr_in* saddr =
reinterpret_cast<const sockaddr_in*>(addr.data());
// Only IPv4 TCP and IPv6 TCP are supported.
if (saddr->sin_family != AF_INET && saddr->sin_family != AF_INET6) {
SendResult(-1, EINVAL);
if (!((addr.size() == sizeof(sockaddr_in) && saddr->sin_family == AF_INET) ||
(addr.size() == sizeof(sockaddr_in6) &&
saddr->sin_family == AF_INET6))) {
SendError(EINVAL);
return;
}
int new_socket = socket(saddr->sin_family, SOCK_STREAM, 0);
if (new_socket < 0) {
SendError(errno);
return;
}
file_util::fileops::FDCloser new_socket_closer(new_socket);
int result = connect(
new_socket, reinterpret_cast<const sockaddr*>(addr.data()), addr.size());
SendResult(result, errno);
if (result == 0 && !fatal_error_) {
if (!comms_->SendFD(new_socket)) {
fatal_error_ = true;
return;
if (result == 0) {
NotifySuccess();
if (!fatal_error_) {
if (!comms_->SendFD(new_socket)) {
fatal_error_ = true;
return;
}
}
}
}
@ -77,8 +80,14 @@ void NetworkProxyServer::Run() {
<< "Clean shutdown or error occurred, shutting down NetworkProxyServer";
}
void NetworkProxyServer::SendResult(int result, int saved_errno) {
if (!comms_->SendInt32(result == 0 ? result : saved_errno)) {
void NetworkProxyServer::SendError(int saved_errno) {
if (!comms_->SendInt32(saved_errno)) {
fatal_error_ = true;
}
}
void NetworkProxyServer::NotifySuccess() {
if (!comms_->SendInt32(0)) {
fatal_error_ = true;
}
}

View File

@ -35,11 +35,13 @@ class NetworkProxyServer {
void Run();
private:
// Sends the result of internal functions to the sandboxee. It sends errno in
// case of error and 0 if no error occurred. On error, it sets fatal_error_ to
// true, which terminates the processing loop in ProcessConnectRequest().
void SendResult(int result, int saved_errno);
// Notifies the network proxy client about the error and sends its code.
void SendError(int saved_errno);
// Notifies the network proxy client that no error occurred.
void NotifySuccess();
// Serves connection requests from the network proxy client.
void ProcessConnectRequest();
std::unique_ptr<Comms> comms_;

View File

@ -23,14 +23,17 @@
#endif
#include <fcntl.h> // For the fcntl flags
#include <linux/futex.h>
#include <linux/net.h> // For SYS_CONNECT
#include <linux/random.h> // For GRND_NONBLOCK
#include <sys/mman.h> // For mmap arguments
#include <sys/socket.h>
#include <syscall.h>
#include <csignal>
#include <cstdint>
#include <utility>
#include "absl/memory/memory.h"
#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "sandboxed_api/sandbox2/namespace.h"
@ -557,31 +560,33 @@ PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall(unsigned int num, BpfFunc f) {
PolicyBuilder& PolicyBuilder::AddPolicyOnSyscalls(
SyscallInitializer nums, const std::vector<sock_filter>& policy) {
auto resolved_policy = ResolveBpfFunc([nums, policy](bpf_labels& labels)
-> std::vector<sock_filter> {
std::vector<sock_filter> out;
out.reserve(nums.size() + policy.size());
for (auto num : nums) {
out.insert(out.end(), {SYSCALL(num, JUMP(&labels, do_policy_l))});
}
out.insert(out.end(),
{JUMP(&labels, dont_do_policy_l), LABEL(&labels, do_policy_l)});
for (const auto& filter : policy) {
// Syscall arch is expected as TRACE value
if (filter.code == (BPF_RET | BPF_K) &&
(filter.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE &&
(filter.k & SECCOMP_RET_DATA) != Syscall::GetHostArch()) {
LOG(WARNING) << "SANDBOX2_TRACE should be used in policy instead of "
"TRACE(value)";
out.push_back(SANDBOX2_TRACE);
} else {
out.push_back(filter);
}
}
out.push_back(LOAD_SYSCALL_NR);
out.insert(out.end(), {LABEL(&labels, dont_do_policy_l)});
return out;
});
auto resolved_policy =
ResolveBpfFunc(
[nums, policy](bpf_labels& labels) -> std::vector<sock_filter> {
std::vector<sock_filter> out;
out.reserve(nums.size() + policy.size());
for (auto num : nums) {
out.insert(out.end(), {SYSCALL(num, JUMP(&labels, do_policy_l))});
}
out.insert(out.end(), {JUMP(&labels, dont_do_policy_l),
LABEL(&labels, do_policy_l)});
for (const auto& filter : policy) {
// Syscall arch is expected as TRACE value
if (filter.code == (BPF_RET | BPF_K) &&
(filter.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE &&
(filter.k & SECCOMP_RET_DATA) != Syscall::GetHostArch()) {
LOG(WARNING)
<< "SANDBOX2_TRACE should be used in policy instead of "
"TRACE(value)";
out.push_back(SANDBOX2_TRACE);
} else {
out.push_back(filter);
}
}
out.push_back(LOAD_SYSCALL_NR);
out.insert(out.end(), {LABEL(&labels, dont_do_policy_l)});
return out;
});
// Pre-/Postcondition: Syscall number loaded into A register
output_->user_policy_.insert(output_->user_policy_.end(),
resolved_policy.begin(), resolved_policy.end());
@ -840,6 +845,66 @@ PolicyBuilder& PolicyBuilder::CollectStacktracesOnKill(bool enable) {
return *this;
}
PolicyBuilder& PolicyBuilder::AddNetworkProxyPolicy() {
AllowFutexOp(FUTEX_WAKE);
AllowFutexOp(FUTEX_WAIT);
AllowFutexOp(FUTEX_WAIT_BITSET);
AllowSyscalls({
__NR_dup2,
__NR_recvmsg,
__NR_close,
__NR_gettid,
});
AddPolicyOnSyscall(__NR_socket, {
ARG_32(0),
JEQ32(AF_INET, ALLOW),
JEQ32(AF_INET6, ALLOW),
});
AddPolicyOnSyscall(__NR_getsockopt,
[](bpf_labels& labels) -> std::vector<sock_filter> {
return {
ARG_32(1),
JNE32(SOL_SOCKET, JUMP(&labels, getsockopt_end)),
ARG_32(2),
JEQ32(SO_TYPE, ALLOW),
LABEL(&labels, getsockopt_end),
};
});
#if defined(__powerpc64__)
AddPolicyOnSyscall(__NR_socketcall, {
ARG_32(0),
JEQ32(SYS_SOCKET, ALLOW),
JEQ32(SYS_GETSOCKOPT, ALLOW),
JEQ32(SYS_RECVMSG, ALLOW),
});
#endif
return *this;
}
PolicyBuilder& PolicyBuilder::AddNetworkProxyHandlerPolicy() {
AddNetworkProxyPolicy();
AllowSyscall(__NR_rt_sigreturn);
AddPolicyOnSyscall(__NR_rt_sigaction, {
ARG_32(0),
JEQ32(SIGSYS, ALLOW),
});
AddPolicyOnSyscall(__NR_rt_sigprocmask, {
ARG_32(0),
JEQ32(SIG_UNBLOCK, ALLOW),
});
AddPolicyOnSyscall(__NR_connect, {TRAP(0)});
#if defined(__powerpc64__)
AddPolicyOnSyscall(__NR_socketcall, {
ARG_32(0),
JEQ32(SYS_CONNECT, TRAP(0)),
});
#endif
return *this;
}
void PolicyBuilder::StoreDescription(PolicyBuilderDescription* pb_description) {
for (const auto& handled_syscall : handled_syscalls_) {
pb_description->add_handled_syscalls(handled_syscall);

View File

@ -497,6 +497,13 @@ class PolicyBuilder final {
// sandbox-team@ first if unsure.
PolicyBuilder& DangerDefaultAllowAll();
// Allows syscalls that are necessary for the NetworkProxyClient
PolicyBuilder& AddNetworkProxyPolicy();
// Allows syscalls that are necessary for the NetworkProxyClient and
// the NetworkProxyHandler
PolicyBuilder& AddNetworkProxyHandlerPolicy();
private:
friend class PolicyBuilderPeer; // For testing
friend class StackTracePeer;