blob: 6a306b2216dc8195b91445a15822893004de88f8 [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);
54
55 int child_stdin = -1, child_stdout = -1, child_stderr = -1;
56
57 std::vector<char*> args_ptr;
58
59 args_ptr.push_back(const_cast<char*>(kSystemProxyWorkerBin));
60 args_ptr.push_back(nullptr);
61
62 // Execute the command.
63 int res =
64 minijail_run_pid_pipes(jail_.get(), args_ptr[0], args_ptr.data(), &pid_,
65 &child_stdin, &child_stdout, &child_stderr);
66
67 if (res != 0) {
68 LOG(ERROR) << "Failed to start sandboxed worker: " << strerror(-res);
Andreea Costinasc9defae2020-04-22 10:28:35 +020069 return false;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010070 }
71
72 // Make sure the pipes never block.
73 if (!base::SetNonBlocking(child_stdin))
74 LOG(WARNING) << "Failed to set stdin non-blocking";
75 if (!base::SetNonBlocking(child_stdout))
76 LOG(WARNING) << "Failed to set stdout non-blocking";
77 if (!base::SetNonBlocking(child_stderr))
78 LOG(WARNING) << "Failed to set stderr non-blocking";
79
80 stdin_pipe_.reset(child_stdin);
81 stdout_pipe_.reset(child_stdout);
82 stderr_pipe_.reset(child_stderr);
83
84 stdout_watcher_ = base::FileDescriptorWatcher::WatchReadable(
85 stdout_pipe_.get(),
86 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
87 base::Unretained(this)));
88
89 stderr_watcher_ = base::FileDescriptorWatcher::WatchReadable(
Andreea Costinasc9defae2020-04-22 10:28:35 +020090 stderr_pipe_.get(), base::BindRepeating(&SandboxedWorker::OnErrorReceived,
91 base::Unretained(this)));
92 return true;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010093}
94
Andreea Costinasdb2cbee2020-06-15 11:43:44 +020095void SandboxedWorker::SetCredentials(const worker::Credentials& credentials) {
Andreea Costinasaae97382020-05-05 13:31:58 +020096 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +010097 *configs.mutable_credentials() = credentials;
98 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
99 LOG(ERROR) << "Failed to set credentials for worker " << pid_;
100 }
101}
102
Andreea Costinasc9defae2020-04-22 10:28:35 +0200103bool SandboxedWorker::SetListeningAddress(uint32_t addr, int port) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200104 worker::SocketAddress address;
Andreea Costinas41e06442020-03-09 09:41:51 +0100105 address.set_addr(addr);
106 address.set_port(port);
Andreea Costinasaae97382020-05-05 13:31:58 +0200107 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100108 *configs.mutable_listening_address() = address;
109
110 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
Andreea Costinasc9defae2020-04-22 10:28:35 +0200111 LOG(ERROR) << "Failed to set local proxy address for worker " << pid_;
112 return false;
Andreea Costinas41e06442020-03-09 09:41:51 +0100113 }
Andreea Costinasa89309d2020-05-08 15:51:12 +0200114 local_proxy_host_and_port_ = base::StringPrintf(
115 "%s:%d", patchpanel::IPv4AddressToString(addr).c_str(), port);
116 LOG(INFO) << "Set proxy address " << local_proxy_host_and_port_
117 << " for worker " << pid_;
Andreea Costinasc9defae2020-04-22 10:28:35 +0200118 return true;
Andreea Costinas41e06442020-03-09 09:41:51 +0100119}
120
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200121bool SandboxedWorker::SetKerberosEnabled(bool enabled,
122 const std::string& krb5_conf_path,
123 const std::string& krb5_ccache_path) {
124 worker::KerberosConfig kerberos_config;
125 kerberos_config.set_enabled(enabled);
126 kerberos_config.set_krb5cc_path(krb5_ccache_path);
127 kerberos_config.set_krb5conf_path(krb5_conf_path);
128 worker::WorkerConfigs configs;
129 *configs.mutable_kerberos_config() = kerberos_config;
130
131 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
132 LOG(ERROR) << "Failed to set kerberos enabled for worker " << pid_;
133 return false;
134 }
135 return true;
136}
137
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100138bool SandboxedWorker::Stop() {
139 if (is_being_terminated_)
140 return true;
141 LOG(INFO) << "Killing " << pid_;
142 is_being_terminated_ = true;
143
144 if (kill(pid_, SIGTERM) < 0) {
145 if (errno == ESRCH) {
146 // No process or group found for pid, assume already terminated.
147 return true;
148 }
149 PLOG(ERROR) << "Failed to terminate process " << pid_;
150 return false;
151 }
152 return true;
153}
154
155bool SandboxedWorker::IsRunning() {
156 return pid_ != 0 && !is_being_terminated_;
157}
158
159void SandboxedWorker::OnMessageReceived() {
Andreea Costinasaae97382020-05-05 13:31:58 +0200160 worker::WorkerRequest request;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100161
162 if (!ReadProtobuf(stdout_pipe_.get(), &request)) {
163 LOG(ERROR) << "Failed to read request from worker " << pid_;
164 // The message is corrupted or the pipe closed, either way stop listening.
165 stdout_watcher_ = nullptr;
166 return;
167 }
168 if (request.has_log_request()) {
169 LOG(INFO) << "[worker: " << pid_ << "]" << request.log_request().message();
170 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100171
172 if (request.has_proxy_resolution_request()) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200173 const worker::ProxyResolutionRequest& proxy_request =
Andreea Costinas5862b102020-03-19 14:45:36 +0100174 request.proxy_resolution_request();
175
176 // This callback will always be called with at least one proxy entry. Even
177 // if the dbus call itself fails, the proxy server list will contain the
178 // direct proxy.
179 adaptor_->GetChromeProxyServersAsync(
180 proxy_request.target_url(),
181 base::BindRepeating(&SandboxedWorker::OnProxyResolved,
182 weak_ptr_factory_.GetWeakPtr(),
183 proxy_request.target_url()));
184 }
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200185 if (request.has_auth_required_request()) {
186 const worker::AuthRequiredRequest& auth_request =
187 request.auth_required_request();
188 adaptor_->RequestAuthenticationCredentials(auth_request.protection_space());
189 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100190}
191
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200192void SandboxedWorker::SetNetNamespaceLifelineFd(
193 base::ScopedFD net_namespace_lifeline_fd) {
194 // Sanity check that only one network namespace is setup for the worker
195 // process.
Andreea Costinasbaa1dc02020-05-20 16:25:21 +0200196 DCHECK(!net_namespace_lifeline_fd_.is_valid());
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200197 net_namespace_lifeline_fd_ = std::move(net_namespace_lifeline_fd);
198}
199
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100200void SandboxedWorker::OnErrorReceived() {
201 std::vector<char> buf;
202 buf.resize(kWorkerBufferSize);
203
204 std::string message;
205 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
206
207 ssize_t count = kWorkerBufferSize;
208 ssize_t total_count = 0;
209
210 while (count == kWorkerBufferSize) {
211 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
212
213 if (count < 0) {
214 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
215 return;
216 }
217
218 if (count == 0) {
219 if (!message.empty())
220 break; // Full message was read at the first iteration.
221
222 PLOG(INFO) << worker_msg << "Pipe closed";
223 // Stop watching, otherwise the handler will fire forever.
224 stderr_watcher_ = nullptr;
225 }
226
227 total_count += count;
228 if (total_count > kMaxWorkerMessageSize) {
229 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
230 "maximum allowed";
231 stderr_watcher_ = nullptr;
232 return;
233 }
234 message.append(buf.begin(), buf.begin() + count);
235 }
236
237 LOG(ERROR) << worker_msg << message;
238}
239
Andreea Costinas5862b102020-03-19 14:45:36 +0100240void SandboxedWorker::OnProxyResolved(
241 const std::string& target_url,
242 bool success,
243 const std::vector<std::string>& proxy_servers) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200244 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100245 reply.set_target_url(target_url);
246
247 // Only http and direct proxies are supported at the moment.
248 for (const auto& proxy : proxy_servers) {
249 if (base::StartsWith(proxy, kPrefixHttp,
250 base::CompareCase::INSENSITIVE_ASCII) ||
251 base::StartsWith(proxy, kPrefixDirect,
252 base::CompareCase::INSENSITIVE_ASCII)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200253 // Make sure the local proxy doesn't try to connect to itself.
254 if (proxy.find(local_proxy_host_and_port()) == std::string::npos) {
255 reply.add_proxy_servers(proxy);
256 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100257 }
258 }
259
Andreea Costinasaae97382020-05-05 13:31:58 +0200260 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100261 *configs.mutable_proxy_resolution_reply() = reply;
262
263 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
264 LOG(ERROR) << "Failed to send proxy resolution reply to worker" << pid_;
265 }
266}
267
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100268} // namespace system_proxy