blob: 5bee9457fe7b14d98055d8769dab8b3b03b427c6 [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>
Qijiang Fan713061e2021-03-08 15:45:12 +090017#include <base/check.h>
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010018#include <base/files/file_util.h>
19#include <base/strings/string_util.h>
Andreea Costinasa89309d2020-05-08 15:51:12 +020020#include <base/strings/stringprintf.h>
Andreea Costinas5862b102020-03-19 14:45:36 +010021#include <brillo/http/http_transport.h>
Andreea Costinasa89309d2020-05-08 15:51:12 +020022#include <chromeos/patchpanel/net_util.h>
Andreea Costinas5862b102020-03-19 14:45:36 +010023#include <google/protobuf/repeated_field.h>
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010024
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010025#include "system-proxy/protobuf_util.h"
Andreea Costinas5862b102020-03-19 14:45:36 +010026#include "system-proxy/system_proxy_adaptor.h"
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010027
28namespace {
29constexpr char kSystemProxyWorkerBin[] = "/usr/sbin/system_proxy_worker";
30constexpr char kSeccompFilterPath[] =
31 "/usr/share/policy/system-proxy-worker-seccomp.policy";
Andreea Costinas350e4aa2020-07-20 20:29:46 +020032constexpr int kMaxWorkerMessageSize = 4096;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010033// Size of the buffer array used to read data from the worker's stderr.
34constexpr int kWorkerBufferSize = 1024;
Andreea Costinas5862b102020-03-19 14:45:36 +010035constexpr char kPrefixDirect[] = "direct://";
36constexpr char kPrefixHttp[] = "http://";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010037} // namespace
38
39namespace system_proxy {
40
Andreea Costinas5862b102020-03-19 14:45:36 +010041SandboxedWorker::SandboxedWorker(base::WeakPtr<SystemProxyAdaptor> adaptor)
Andreea Costinas396c1de2020-04-12 23:44:46 +020042 : jail_(minijail_new()), adaptor_(adaptor), pid_(0) {}
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010043
Andreea Costinasc9defae2020-04-22 10:28:35 +020044bool SandboxedWorker::Start() {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010045 DCHECK(!IsRunning()) << "Worker is already running.";
46
47 if (!jail_)
Andreea Costinasc9defae2020-04-22 10:28:35 +020048 return false;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010049
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020050 minijail_namespace_pids(jail_.get());
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010051 minijail_namespace_net(jail_.get());
52 minijail_no_new_privs(jail_.get());
53 minijail_use_seccomp_filter(jail_.get());
54 minijail_parse_seccomp_filters(jail_.get(), kSeccompFilterPath);
Andreea Costinas005a5d22020-07-17 15:09:08 +020055 // Required to forward SIGTERM to the child process.
56 minijail_forward_signals(jail_.get());
57 // Resets the signal mask to ensure signals are not unintentionally blocked.
58 minijail_reset_signal_mask(jail_.get());
59 // Resets the signal handlers to the default behaviours. This is needed so
60 // that the child process terminates when receiving the SIGTERM signal.
61 minijail_reset_signal_handlers(jail_.get());
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010062
63 int child_stdin = -1, child_stdout = -1, child_stderr = -1;
64
65 std::vector<char*> args_ptr;
66
67 args_ptr.push_back(const_cast<char*>(kSystemProxyWorkerBin));
68 args_ptr.push_back(nullptr);
69
70 // Execute the command.
71 int res =
72 minijail_run_pid_pipes(jail_.get(), args_ptr[0], args_ptr.data(), &pid_,
73 &child_stdin, &child_stdout, &child_stderr);
74
75 if (res != 0) {
76 LOG(ERROR) << "Failed to start sandboxed worker: " << strerror(-res);
Andreea Costinasc9defae2020-04-22 10:28:35 +020077 return false;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010078 }
79
80 // Make sure the pipes never block.
81 if (!base::SetNonBlocking(child_stdin))
82 LOG(WARNING) << "Failed to set stdin non-blocking";
83 if (!base::SetNonBlocking(child_stdout))
84 LOG(WARNING) << "Failed to set stdout non-blocking";
85 if (!base::SetNonBlocking(child_stderr))
86 LOG(WARNING) << "Failed to set stderr non-blocking";
87
88 stdin_pipe_.reset(child_stdin);
89 stdout_pipe_.reset(child_stdout);
90 stderr_pipe_.reset(child_stderr);
91
92 stdout_watcher_ = base::FileDescriptorWatcher::WatchReadable(
93 stdout_pipe_.get(),
94 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
95 base::Unretained(this)));
96
97 stderr_watcher_ = base::FileDescriptorWatcher::WatchReadable(
Andreea Costinasc9defae2020-04-22 10:28:35 +020098 stderr_pipe_.get(), base::BindRepeating(&SandboxedWorker::OnErrorReceived,
99 base::Unretained(this)));
100 return true;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100101}
102
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200103void SandboxedWorker::SetCredentials(const worker::Credentials& credentials) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200104 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100105 *configs.mutable_credentials() = credentials;
106 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
107 LOG(ERROR) << "Failed to set credentials for worker " << pid_;
108 }
109}
110
Andreea Costinasc9defae2020-04-22 10:28:35 +0200111bool SandboxedWorker::SetListeningAddress(uint32_t addr, int port) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200112 worker::SocketAddress address;
Andreea Costinas41e06442020-03-09 09:41:51 +0100113 address.set_addr(addr);
114 address.set_port(port);
Andreea Costinasaae97382020-05-05 13:31:58 +0200115 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100116 *configs.mutable_listening_address() = address;
117
118 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
Andreea Costinasc9defae2020-04-22 10:28:35 +0200119 LOG(ERROR) << "Failed to set local proxy address for worker " << pid_;
120 return false;
Andreea Costinas41e06442020-03-09 09:41:51 +0100121 }
Andreea Costinasa89309d2020-05-08 15:51:12 +0200122 local_proxy_host_and_port_ = base::StringPrintf(
123 "%s:%d", patchpanel::IPv4AddressToString(addr).c_str(), port);
124 LOG(INFO) << "Set proxy address " << local_proxy_host_and_port_
125 << " for worker " << pid_;
Andreea Costinasc9defae2020-04-22 10:28:35 +0200126 return true;
Andreea Costinas41e06442020-03-09 09:41:51 +0100127}
128
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200129bool SandboxedWorker::SetKerberosEnabled(bool enabled,
130 const std::string& krb5_conf_path,
131 const std::string& krb5_ccache_path) {
132 worker::KerberosConfig kerberos_config;
133 kerberos_config.set_enabled(enabled);
134 kerberos_config.set_krb5cc_path(krb5_ccache_path);
135 kerberos_config.set_krb5conf_path(krb5_conf_path);
136 worker::WorkerConfigs configs;
137 *configs.mutable_kerberos_config() = kerberos_config;
138
139 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
140 LOG(ERROR) << "Failed to set kerberos enabled for worker " << pid_;
141 return false;
142 }
143 return true;
144}
145
Andreea Costinase9c73592020-07-17 15:27:54 +0200146bool SandboxedWorker::ClearUserCredentials() {
147 worker::ClearUserCredentials clear_user_credentials;
148 worker::WorkerConfigs configs;
149 *configs.mutable_clear_user_credentials() = clear_user_credentials;
150
151 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
152 LOG(ERROR) << "Failed to send request to clear user credentials for worker "
153 << pid_;
154 return false;
155 }
156 return true;
157}
158
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100159bool SandboxedWorker::Stop() {
160 if (is_being_terminated_)
161 return true;
162 LOG(INFO) << "Killing " << pid_;
163 is_being_terminated_ = true;
164
165 if (kill(pid_, SIGTERM) < 0) {
166 if (errno == ESRCH) {
167 // No process or group found for pid, assume already terminated.
168 return true;
169 }
170 PLOG(ERROR) << "Failed to terminate process " << pid_;
171 return false;
172 }
173 return true;
174}
175
176bool SandboxedWorker::IsRunning() {
177 return pid_ != 0 && !is_being_terminated_;
178}
179
180void SandboxedWorker::OnMessageReceived() {
Andreea Costinasaae97382020-05-05 13:31:58 +0200181 worker::WorkerRequest request;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100182
183 if (!ReadProtobuf(stdout_pipe_.get(), &request)) {
184 LOG(ERROR) << "Failed to read request from worker " << pid_;
185 // The message is corrupted or the pipe closed, either way stop listening.
186 stdout_watcher_ = nullptr;
187 return;
188 }
189 if (request.has_log_request()) {
190 LOG(INFO) << "[worker: " << pid_ << "]" << request.log_request().message();
191 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100192
193 if (request.has_proxy_resolution_request()) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200194 const worker::ProxyResolutionRequest& proxy_request =
Andreea Costinas5862b102020-03-19 14:45:36 +0100195 request.proxy_resolution_request();
196
197 // This callback will always be called with at least one proxy entry. Even
198 // if the dbus call itself fails, the proxy server list will contain the
199 // direct proxy.
200 adaptor_->GetChromeProxyServersAsync(
201 proxy_request.target_url(),
202 base::BindRepeating(&SandboxedWorker::OnProxyResolved,
203 weak_ptr_factory_.GetWeakPtr(),
204 proxy_request.target_url()));
205 }
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200206 if (request.has_auth_required_request()) {
207 const worker::AuthRequiredRequest& auth_request =
208 request.auth_required_request();
Andreea Costinased9e6122020-08-12 12:06:19 +0200209 adaptor_->RequestAuthenticationCredentials(
210 auth_request.protection_space(), auth_request.bad_cached_credentials());
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200211 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100212}
213
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200214void SandboxedWorker::SetNetNamespaceLifelineFd(
215 base::ScopedFD net_namespace_lifeline_fd) {
216 // Sanity check that only one network namespace is setup for the worker
217 // process.
Andreea Costinasbaa1dc02020-05-20 16:25:21 +0200218 DCHECK(!net_namespace_lifeline_fd_.is_valid());
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200219 net_namespace_lifeline_fd_ = std::move(net_namespace_lifeline_fd);
220}
221
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100222void SandboxedWorker::OnErrorReceived() {
223 std::vector<char> buf;
224 buf.resize(kWorkerBufferSize);
225
226 std::string message;
227 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
228
229 ssize_t count = kWorkerBufferSize;
230 ssize_t total_count = 0;
231
232 while (count == kWorkerBufferSize) {
233 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
234
235 if (count < 0) {
236 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
237 return;
238 }
239
240 if (count == 0) {
241 if (!message.empty())
242 break; // Full message was read at the first iteration.
243
244 PLOG(INFO) << worker_msg << "Pipe closed";
245 // Stop watching, otherwise the handler will fire forever.
246 stderr_watcher_ = nullptr;
247 }
248
249 total_count += count;
250 if (total_count > kMaxWorkerMessageSize) {
251 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
252 "maximum allowed";
253 stderr_watcher_ = nullptr;
254 return;
255 }
256 message.append(buf.begin(), buf.begin() + count);
257 }
258
259 LOG(ERROR) << worker_msg << message;
260}
261
Andreea Costinas5862b102020-03-19 14:45:36 +0100262void SandboxedWorker::OnProxyResolved(
263 const std::string& target_url,
264 bool success,
265 const std::vector<std::string>& proxy_servers) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200266 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100267 reply.set_target_url(target_url);
268
269 // Only http and direct proxies are supported at the moment.
270 for (const auto& proxy : proxy_servers) {
271 if (base::StartsWith(proxy, kPrefixHttp,
272 base::CompareCase::INSENSITIVE_ASCII) ||
273 base::StartsWith(proxy, kPrefixDirect,
274 base::CompareCase::INSENSITIVE_ASCII)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200275 // Make sure the local proxy doesn't try to connect to itself.
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200276 if (!adaptor_->IsLocalProxy(proxy)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200277 reply.add_proxy_servers(proxy);
278 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100279 }
280 }
281
Andreea Costinasaae97382020-05-05 13:31:58 +0200282 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100283 *configs.mutable_proxy_resolution_reply() = reply;
284
285 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
286 LOG(ERROR) << "Failed to send proxy resolution reply to worker" << pid_;
287 }
288}
289
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100290} // namespace system_proxy