blob: 8fd772e4cc64ebd000028a1067ce02ff8168e40d [file] [log] [blame]
Andreea Costinasc7d5ad02020-03-09 09:41:51 +01001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "system-proxy/sandboxed_worker.h"
6
7#include <inttypes.h>
8#include <stdlib.h>
9#include <sys/types.h>
10
11#include <string>
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020012#include <utility>
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010013#include <vector>
14
15#include <base/bind.h>
16#include <base/callback_helpers.h>
17#include <base/files/file_util.h>
18#include <base/strings/string_util.h>
Andreea Costinasa89309d2020-05-08 15:51:12 +020019#include <base/strings/stringprintf.h>
Andreea Costinas5862b102020-03-19 14:45:36 +010020#include <brillo/http/http_transport.h>
Andreea Costinasa89309d2020-05-08 15:51:12 +020021#include <chromeos/patchpanel/net_util.h>
Andreea Costinas5862b102020-03-19 14:45:36 +010022#include <google/protobuf/repeated_field.h>
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010023
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010024#include "system-proxy/protobuf_util.h"
Andreea Costinas5862b102020-03-19 14:45:36 +010025#include "system-proxy/system_proxy_adaptor.h"
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010026
27namespace {
28constexpr char kSystemProxyWorkerBin[] = "/usr/sbin/system_proxy_worker";
29constexpr char kSeccompFilterPath[] =
30 "/usr/share/policy/system-proxy-worker-seccomp.policy";
31constexpr int kMaxWorkerMessageSize = 2048;
32// Size of the buffer array used to read data from the worker's stderr.
33constexpr int kWorkerBufferSize = 1024;
Andreea Costinas5862b102020-03-19 14:45:36 +010034constexpr char kPrefixDirect[] = "direct://";
35constexpr char kPrefixHttp[] = "http://";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010036} // namespace
37
38namespace system_proxy {
39
Andreea Costinas5862b102020-03-19 14:45:36 +010040SandboxedWorker::SandboxedWorker(base::WeakPtr<SystemProxyAdaptor> adaptor)
Andreea Costinas396c1de2020-04-12 23:44:46 +020041 : jail_(minijail_new()), adaptor_(adaptor), pid_(0) {}
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010042
Andreea Costinasc9defae2020-04-22 10:28:35 +020043bool SandboxedWorker::Start() {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010044 DCHECK(!IsRunning()) << "Worker is already running.";
45
46 if (!jail_)
Andreea Costinasc9defae2020-04-22 10:28:35 +020047 return false;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010048
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020049 minijail_namespace_pids(jail_.get());
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010050 minijail_namespace_net(jail_.get());
51 minijail_no_new_privs(jail_.get());
52 minijail_use_seccomp_filter(jail_.get());
53 minijail_parse_seccomp_filters(jail_.get(), kSeccompFilterPath);
Andreea Costinas005a5d22020-07-17 15:09:08 +020054 // Required to forward SIGTERM to the child process.
55 minijail_forward_signals(jail_.get());
56 // Resets the signal mask to ensure signals are not unintentionally blocked.
57 minijail_reset_signal_mask(jail_.get());
58 // Resets the signal handlers to the default behaviours. This is needed so
59 // that the child process terminates when receiving the SIGTERM signal.
60 minijail_reset_signal_handlers(jail_.get());
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010061
62 int child_stdin = -1, child_stdout = -1, child_stderr = -1;
63
64 std::vector<char*> args_ptr;
65
66 args_ptr.push_back(const_cast<char*>(kSystemProxyWorkerBin));
67 args_ptr.push_back(nullptr);
68
69 // Execute the command.
70 int res =
71 minijail_run_pid_pipes(jail_.get(), args_ptr[0], args_ptr.data(), &pid_,
72 &child_stdin, &child_stdout, &child_stderr);
73
74 if (res != 0) {
75 LOG(ERROR) << "Failed to start sandboxed worker: " << strerror(-res);
Andreea Costinasc9defae2020-04-22 10:28:35 +020076 return false;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010077 }
78
79 // Make sure the pipes never block.
80 if (!base::SetNonBlocking(child_stdin))
81 LOG(WARNING) << "Failed to set stdin non-blocking";
82 if (!base::SetNonBlocking(child_stdout))
83 LOG(WARNING) << "Failed to set stdout non-blocking";
84 if (!base::SetNonBlocking(child_stderr))
85 LOG(WARNING) << "Failed to set stderr non-blocking";
86
87 stdin_pipe_.reset(child_stdin);
88 stdout_pipe_.reset(child_stdout);
89 stderr_pipe_.reset(child_stderr);
90
91 stdout_watcher_ = base::FileDescriptorWatcher::WatchReadable(
92 stdout_pipe_.get(),
93 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
94 base::Unretained(this)));
95
96 stderr_watcher_ = base::FileDescriptorWatcher::WatchReadable(
Andreea Costinasc9defae2020-04-22 10:28:35 +020097 stderr_pipe_.get(), base::BindRepeating(&SandboxedWorker::OnErrorReceived,
98 base::Unretained(this)));
99 return true;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100100}
101
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200102void SandboxedWorker::SetCredentials(const worker::Credentials& credentials) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200103 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100104 *configs.mutable_credentials() = credentials;
105 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
106 LOG(ERROR) << "Failed to set credentials for worker " << pid_;
107 }
108}
109
Andreea Costinasc9defae2020-04-22 10:28:35 +0200110bool SandboxedWorker::SetListeningAddress(uint32_t addr, int port) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200111 worker::SocketAddress address;
Andreea Costinas41e06442020-03-09 09:41:51 +0100112 address.set_addr(addr);
113 address.set_port(port);
Andreea Costinasaae97382020-05-05 13:31:58 +0200114 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100115 *configs.mutable_listening_address() = address;
116
117 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
Andreea Costinasc9defae2020-04-22 10:28:35 +0200118 LOG(ERROR) << "Failed to set local proxy address for worker " << pid_;
119 return false;
Andreea Costinas41e06442020-03-09 09:41:51 +0100120 }
Andreea Costinasa89309d2020-05-08 15:51:12 +0200121 local_proxy_host_and_port_ = base::StringPrintf(
122 "%s:%d", patchpanel::IPv4AddressToString(addr).c_str(), port);
123 LOG(INFO) << "Set proxy address " << local_proxy_host_and_port_
124 << " for worker " << pid_;
Andreea Costinasc9defae2020-04-22 10:28:35 +0200125 return true;
Andreea Costinas41e06442020-03-09 09:41:51 +0100126}
127
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200128bool SandboxedWorker::SetKerberosEnabled(bool enabled,
129 const std::string& krb5_conf_path,
130 const std::string& krb5_ccache_path) {
131 worker::KerberosConfig kerberos_config;
132 kerberos_config.set_enabled(enabled);
133 kerberos_config.set_krb5cc_path(krb5_ccache_path);
134 kerberos_config.set_krb5conf_path(krb5_conf_path);
135 worker::WorkerConfigs configs;
136 *configs.mutable_kerberos_config() = kerberos_config;
137
138 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
139 LOG(ERROR) << "Failed to set kerberos enabled for worker " << pid_;
140 return false;
141 }
142 return true;
143}
144
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100145bool SandboxedWorker::Stop() {
146 if (is_being_terminated_)
147 return true;
148 LOG(INFO) << "Killing " << pid_;
149 is_being_terminated_ = true;
150
151 if (kill(pid_, SIGTERM) < 0) {
152 if (errno == ESRCH) {
153 // No process or group found for pid, assume already terminated.
154 return true;
155 }
156 PLOG(ERROR) << "Failed to terminate process " << pid_;
157 return false;
158 }
159 return true;
160}
161
162bool SandboxedWorker::IsRunning() {
163 return pid_ != 0 && !is_being_terminated_;
164}
165
166void SandboxedWorker::OnMessageReceived() {
Andreea Costinasaae97382020-05-05 13:31:58 +0200167 worker::WorkerRequest request;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100168
169 if (!ReadProtobuf(stdout_pipe_.get(), &request)) {
170 LOG(ERROR) << "Failed to read request from worker " << pid_;
171 // The message is corrupted or the pipe closed, either way stop listening.
172 stdout_watcher_ = nullptr;
173 return;
174 }
175 if (request.has_log_request()) {
176 LOG(INFO) << "[worker: " << pid_ << "]" << request.log_request().message();
177 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100178
179 if (request.has_proxy_resolution_request()) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200180 const worker::ProxyResolutionRequest& proxy_request =
Andreea Costinas5862b102020-03-19 14:45:36 +0100181 request.proxy_resolution_request();
182
183 // This callback will always be called with at least one proxy entry. Even
184 // if the dbus call itself fails, the proxy server list will contain the
185 // direct proxy.
186 adaptor_->GetChromeProxyServersAsync(
187 proxy_request.target_url(),
188 base::BindRepeating(&SandboxedWorker::OnProxyResolved,
189 weak_ptr_factory_.GetWeakPtr(),
190 proxy_request.target_url()));
191 }
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200192 if (request.has_auth_required_request()) {
193 const worker::AuthRequiredRequest& auth_request =
194 request.auth_required_request();
195 adaptor_->RequestAuthenticationCredentials(auth_request.protection_space());
196 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100197}
198
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200199void SandboxedWorker::SetNetNamespaceLifelineFd(
200 base::ScopedFD net_namespace_lifeline_fd) {
201 // Sanity check that only one network namespace is setup for the worker
202 // process.
Andreea Costinasbaa1dc02020-05-20 16:25:21 +0200203 DCHECK(!net_namespace_lifeline_fd_.is_valid());
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200204 net_namespace_lifeline_fd_ = std::move(net_namespace_lifeline_fd);
205}
206
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100207void SandboxedWorker::OnErrorReceived() {
208 std::vector<char> buf;
209 buf.resize(kWorkerBufferSize);
210
211 std::string message;
212 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
213
214 ssize_t count = kWorkerBufferSize;
215 ssize_t total_count = 0;
216
217 while (count == kWorkerBufferSize) {
218 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
219
220 if (count < 0) {
221 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
222 return;
223 }
224
225 if (count == 0) {
226 if (!message.empty())
227 break; // Full message was read at the first iteration.
228
229 PLOG(INFO) << worker_msg << "Pipe closed";
230 // Stop watching, otherwise the handler will fire forever.
231 stderr_watcher_ = nullptr;
232 }
233
234 total_count += count;
235 if (total_count > kMaxWorkerMessageSize) {
236 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
237 "maximum allowed";
238 stderr_watcher_ = nullptr;
239 return;
240 }
241 message.append(buf.begin(), buf.begin() + count);
242 }
243
244 LOG(ERROR) << worker_msg << message;
245}
246
Andreea Costinas5862b102020-03-19 14:45:36 +0100247void SandboxedWorker::OnProxyResolved(
248 const std::string& target_url,
249 bool success,
250 const std::vector<std::string>& proxy_servers) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200251 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100252 reply.set_target_url(target_url);
253
254 // Only http and direct proxies are supported at the moment.
255 for (const auto& proxy : proxy_servers) {
256 if (base::StartsWith(proxy, kPrefixHttp,
257 base::CompareCase::INSENSITIVE_ASCII) ||
258 base::StartsWith(proxy, kPrefixDirect,
259 base::CompareCase::INSENSITIVE_ASCII)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200260 // Make sure the local proxy doesn't try to connect to itself.
261 if (proxy.find(local_proxy_host_and_port()) == std::string::npos) {
262 reply.add_proxy_servers(proxy);
263 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100264 }
265 }
266
Andreea Costinasaae97382020-05-05 13:31:58 +0200267 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100268 *configs.mutable_proxy_resolution_reply() = reply;
269
270 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
271 LOG(ERROR) << "Failed to send proxy resolution reply to worker" << pid_;
272 }
273}
274
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100275} // namespace system_proxy