blob: 6c45003e67b797e22952659d13c1ccc63116779b [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();
208 adaptor_->RequestAuthenticationCredentials(auth_request.protection_space());
209 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100210}
211
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200212void SandboxedWorker::SetNetNamespaceLifelineFd(
213 base::ScopedFD net_namespace_lifeline_fd) {
214 // Sanity check that only one network namespace is setup for the worker
215 // process.
Andreea Costinasbaa1dc02020-05-20 16:25:21 +0200216 DCHECK(!net_namespace_lifeline_fd_.is_valid());
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200217 net_namespace_lifeline_fd_ = std::move(net_namespace_lifeline_fd);
218}
219
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100220void SandboxedWorker::OnErrorReceived() {
221 std::vector<char> buf;
222 buf.resize(kWorkerBufferSize);
223
224 std::string message;
225 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
226
227 ssize_t count = kWorkerBufferSize;
228 ssize_t total_count = 0;
229
230 while (count == kWorkerBufferSize) {
231 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
232
233 if (count < 0) {
234 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
235 return;
236 }
237
238 if (count == 0) {
239 if (!message.empty())
240 break; // Full message was read at the first iteration.
241
242 PLOG(INFO) << worker_msg << "Pipe closed";
243 // Stop watching, otherwise the handler will fire forever.
244 stderr_watcher_ = nullptr;
245 }
246
247 total_count += count;
248 if (total_count > kMaxWorkerMessageSize) {
249 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
250 "maximum allowed";
251 stderr_watcher_ = nullptr;
252 return;
253 }
254 message.append(buf.begin(), buf.begin() + count);
255 }
256
257 LOG(ERROR) << worker_msg << message;
258}
259
Andreea Costinas5862b102020-03-19 14:45:36 +0100260void SandboxedWorker::OnProxyResolved(
261 const std::string& target_url,
262 bool success,
263 const std::vector<std::string>& proxy_servers) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200264 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100265 reply.set_target_url(target_url);
266
267 // Only http and direct proxies are supported at the moment.
268 for (const auto& proxy : proxy_servers) {
269 if (base::StartsWith(proxy, kPrefixHttp,
270 base::CompareCase::INSENSITIVE_ASCII) ||
271 base::StartsWith(proxy, kPrefixDirect,
272 base::CompareCase::INSENSITIVE_ASCII)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200273 // Make sure the local proxy doesn't try to connect to itself.
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200274 if (!adaptor_->IsLocalProxy(proxy)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200275 reply.add_proxy_servers(proxy);
276 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100277 }
278 }
279
Andreea Costinasaae97382020-05-05 13:31:58 +0200280 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100281 *configs.mutable_proxy_resolution_reply() = reply;
282
283 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
284 LOG(ERROR) << "Failed to send proxy resolution reply to worker" << pid_;
285 }
286}
287
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100288} // namespace system_proxy