blob: 4182ad87d6bef53b63b69279186b55d380fc15f2 [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";
Andreea Costinas350e4aa2020-07-20 20:29:46 +020031constexpr int kMaxWorkerMessageSize = 4096;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010032// 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 Costinase9c73592020-07-17 15:27:54 +0200145bool SandboxedWorker::ClearUserCredentials() {
146 worker::ClearUserCredentials clear_user_credentials;
147 worker::WorkerConfigs configs;
148 *configs.mutable_clear_user_credentials() = clear_user_credentials;
149
150 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
151 LOG(ERROR) << "Failed to send request to clear user credentials for worker "
152 << pid_;
153 return false;
154 }
155 return true;
156}
157
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100158bool SandboxedWorker::Stop() {
159 if (is_being_terminated_)
160 return true;
161 LOG(INFO) << "Killing " << pid_;
162 is_being_terminated_ = true;
163
164 if (kill(pid_, SIGTERM) < 0) {
165 if (errno == ESRCH) {
166 // No process or group found for pid, assume already terminated.
167 return true;
168 }
169 PLOG(ERROR) << "Failed to terminate process " << pid_;
170 return false;
171 }
172 return true;
173}
174
175bool SandboxedWorker::IsRunning() {
176 return pid_ != 0 && !is_being_terminated_;
177}
178
179void SandboxedWorker::OnMessageReceived() {
Andreea Costinasaae97382020-05-05 13:31:58 +0200180 worker::WorkerRequest request;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100181
182 if (!ReadProtobuf(stdout_pipe_.get(), &request)) {
183 LOG(ERROR) << "Failed to read request from worker " << pid_;
184 // The message is corrupted or the pipe closed, either way stop listening.
185 stdout_watcher_ = nullptr;
186 return;
187 }
188 if (request.has_log_request()) {
189 LOG(INFO) << "[worker: " << pid_ << "]" << request.log_request().message();
190 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100191
192 if (request.has_proxy_resolution_request()) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200193 const worker::ProxyResolutionRequest& proxy_request =
Andreea Costinas5862b102020-03-19 14:45:36 +0100194 request.proxy_resolution_request();
195
196 // This callback will always be called with at least one proxy entry. Even
197 // if the dbus call itself fails, the proxy server list will contain the
198 // direct proxy.
199 adaptor_->GetChromeProxyServersAsync(
200 proxy_request.target_url(),
201 base::BindRepeating(&SandboxedWorker::OnProxyResolved,
202 weak_ptr_factory_.GetWeakPtr(),
203 proxy_request.target_url()));
204 }
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200205 if (request.has_auth_required_request()) {
206 const worker::AuthRequiredRequest& auth_request =
207 request.auth_required_request();
Andreea Costinased9e6122020-08-12 12:06:19 +0200208 adaptor_->RequestAuthenticationCredentials(
209 auth_request.protection_space(), auth_request.bad_cached_credentials());
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200210 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100211}
212
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200213void SandboxedWorker::SetNetNamespaceLifelineFd(
214 base::ScopedFD net_namespace_lifeline_fd) {
215 // Sanity check that only one network namespace is setup for the worker
216 // process.
Andreea Costinasbaa1dc02020-05-20 16:25:21 +0200217 DCHECK(!net_namespace_lifeline_fd_.is_valid());
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200218 net_namespace_lifeline_fd_ = std::move(net_namespace_lifeline_fd);
219}
220
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100221void SandboxedWorker::OnErrorReceived() {
222 std::vector<char> buf;
223 buf.resize(kWorkerBufferSize);
224
225 std::string message;
226 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
227
228 ssize_t count = kWorkerBufferSize;
229 ssize_t total_count = 0;
230
231 while (count == kWorkerBufferSize) {
232 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
233
234 if (count < 0) {
235 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
236 return;
237 }
238
239 if (count == 0) {
240 if (!message.empty())
241 break; // Full message was read at the first iteration.
242
243 PLOG(INFO) << worker_msg << "Pipe closed";
244 // Stop watching, otherwise the handler will fire forever.
245 stderr_watcher_ = nullptr;
246 }
247
248 total_count += count;
249 if (total_count > kMaxWorkerMessageSize) {
250 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
251 "maximum allowed";
252 stderr_watcher_ = nullptr;
253 return;
254 }
255 message.append(buf.begin(), buf.begin() + count);
256 }
257
258 LOG(ERROR) << worker_msg << message;
259}
260
Andreea Costinas5862b102020-03-19 14:45:36 +0100261void SandboxedWorker::OnProxyResolved(
262 const std::string& target_url,
263 bool success,
264 const std::vector<std::string>& proxy_servers) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200265 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100266 reply.set_target_url(target_url);
267
268 // Only http and direct proxies are supported at the moment.
269 for (const auto& proxy : proxy_servers) {
270 if (base::StartsWith(proxy, kPrefixHttp,
271 base::CompareCase::INSENSITIVE_ASCII) ||
272 base::StartsWith(proxy, kPrefixDirect,
273 base::CompareCase::INSENSITIVE_ASCII)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200274 // Make sure the local proxy doesn't try to connect to itself.
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200275 if (!adaptor_->IsLocalProxy(proxy)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200276 reply.add_proxy_servers(proxy);
277 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100278 }
279 }
280
Andreea Costinasaae97382020-05-05 13:31:58 +0200281 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100282 *configs.mutable_proxy_resolution_reply() = reply;
283
284 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
285 LOG(ERROR) << "Failed to send proxy resolution reply to worker" << pid_;
286 }
287}
288
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100289} // namespace system_proxy