blob: 6ed24eac37fd50e9301836cab60311dec6e929b1 [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
24#include "bindings/worker_common.pb.h"
25#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";
32constexpr int kMaxWorkerMessageSize = 2048;
33// 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);
55
56 int child_stdin = -1, child_stdout = -1, child_stderr = -1;
57
58 std::vector<char*> args_ptr;
59
60 args_ptr.push_back(const_cast<char*>(kSystemProxyWorkerBin));
61 args_ptr.push_back(nullptr);
62
63 // Execute the command.
64 int res =
65 minijail_run_pid_pipes(jail_.get(), args_ptr[0], args_ptr.data(), &pid_,
66 &child_stdin, &child_stdout, &child_stderr);
67
68 if (res != 0) {
69 LOG(ERROR) << "Failed to start sandboxed worker: " << strerror(-res);
Andreea Costinasc9defae2020-04-22 10:28:35 +020070 return false;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010071 }
72
73 // Make sure the pipes never block.
74 if (!base::SetNonBlocking(child_stdin))
75 LOG(WARNING) << "Failed to set stdin non-blocking";
76 if (!base::SetNonBlocking(child_stdout))
77 LOG(WARNING) << "Failed to set stdout non-blocking";
78 if (!base::SetNonBlocking(child_stderr))
79 LOG(WARNING) << "Failed to set stderr non-blocking";
80
81 stdin_pipe_.reset(child_stdin);
82 stdout_pipe_.reset(child_stdout);
83 stderr_pipe_.reset(child_stderr);
84
85 stdout_watcher_ = base::FileDescriptorWatcher::WatchReadable(
86 stdout_pipe_.get(),
87 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
88 base::Unretained(this)));
89
90 stderr_watcher_ = base::FileDescriptorWatcher::WatchReadable(
Andreea Costinasc9defae2020-04-22 10:28:35 +020091 stderr_pipe_.get(), base::BindRepeating(&SandboxedWorker::OnErrorReceived,
92 base::Unretained(this)));
93 return true;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010094}
95
Andreea Costinas41e06442020-03-09 09:41:51 +010096void SandboxedWorker::SetUsernameAndPassword(const std::string& username,
97 const std::string& password) {
Andreea Costinasaae97382020-05-05 13:31:58 +020098 worker::Credentials credentials;
Andreea Costinas41e06442020-03-09 09:41:51 +010099 credentials.set_username(username);
100 credentials.set_password(password);
Andreea Costinasaae97382020-05-05 13:31:58 +0200101 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100102 *configs.mutable_credentials() = credentials;
103 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
104 LOG(ERROR) << "Failed to set credentials for worker " << pid_;
105 }
106}
107
Andreea Costinasc9defae2020-04-22 10:28:35 +0200108bool SandboxedWorker::SetListeningAddress(uint32_t addr, int port) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200109 worker::SocketAddress address;
Andreea Costinas41e06442020-03-09 09:41:51 +0100110 address.set_addr(addr);
111 address.set_port(port);
Andreea Costinasaae97382020-05-05 13:31:58 +0200112 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100113 *configs.mutable_listening_address() = address;
114
115 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
Andreea Costinasc9defae2020-04-22 10:28:35 +0200116 LOG(ERROR) << "Failed to set local proxy address for worker " << pid_;
117 return false;
Andreea Costinas41e06442020-03-09 09:41:51 +0100118 }
Andreea Costinasa89309d2020-05-08 15:51:12 +0200119 local_proxy_host_and_port_ = base::StringPrintf(
120 "%s:%d", patchpanel::IPv4AddressToString(addr).c_str(), port);
121 LOG(INFO) << "Set proxy address " << local_proxy_host_and_port_
122 << " for worker " << pid_;
Andreea Costinasc9defae2020-04-22 10:28:35 +0200123 return true;
Andreea Costinas41e06442020-03-09 09:41:51 +0100124}
125
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100126bool SandboxedWorker::Stop() {
127 if (is_being_terminated_)
128 return true;
129 LOG(INFO) << "Killing " << pid_;
130 is_being_terminated_ = true;
131
132 if (kill(pid_, SIGTERM) < 0) {
133 if (errno == ESRCH) {
134 // No process or group found for pid, assume already terminated.
135 return true;
136 }
137 PLOG(ERROR) << "Failed to terminate process " << pid_;
138 return false;
139 }
140 return true;
141}
142
143bool SandboxedWorker::IsRunning() {
144 return pid_ != 0 && !is_being_terminated_;
145}
146
147void SandboxedWorker::OnMessageReceived() {
Andreea Costinasaae97382020-05-05 13:31:58 +0200148 worker::WorkerRequest request;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100149
150 if (!ReadProtobuf(stdout_pipe_.get(), &request)) {
151 LOG(ERROR) << "Failed to read request from worker " << pid_;
152 // The message is corrupted or the pipe closed, either way stop listening.
153 stdout_watcher_ = nullptr;
154 return;
155 }
156 if (request.has_log_request()) {
157 LOG(INFO) << "[worker: " << pid_ << "]" << request.log_request().message();
158 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100159
160 if (request.has_proxy_resolution_request()) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200161 const worker::ProxyResolutionRequest& proxy_request =
Andreea Costinas5862b102020-03-19 14:45:36 +0100162 request.proxy_resolution_request();
163
164 // This callback will always be called with at least one proxy entry. Even
165 // if the dbus call itself fails, the proxy server list will contain the
166 // direct proxy.
167 adaptor_->GetChromeProxyServersAsync(
168 proxy_request.target_url(),
169 base::BindRepeating(&SandboxedWorker::OnProxyResolved,
170 weak_ptr_factory_.GetWeakPtr(),
171 proxy_request.target_url()));
172 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100173}
174
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200175void SandboxedWorker::SetNetNamespaceLifelineFd(
176 base::ScopedFD net_namespace_lifeline_fd) {
177 // Sanity check that only one network namespace is setup for the worker
178 // process.
179 DCHECK(!net_namespace_lifeline_fd.is_valid());
180 net_namespace_lifeline_fd_ = std::move(net_namespace_lifeline_fd);
181}
182
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100183void SandboxedWorker::OnErrorReceived() {
184 std::vector<char> buf;
185 buf.resize(kWorkerBufferSize);
186
187 std::string message;
188 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
189
190 ssize_t count = kWorkerBufferSize;
191 ssize_t total_count = 0;
192
193 while (count == kWorkerBufferSize) {
194 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
195
196 if (count < 0) {
197 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
198 return;
199 }
200
201 if (count == 0) {
202 if (!message.empty())
203 break; // Full message was read at the first iteration.
204
205 PLOG(INFO) << worker_msg << "Pipe closed";
206 // Stop watching, otherwise the handler will fire forever.
207 stderr_watcher_ = nullptr;
208 }
209
210 total_count += count;
211 if (total_count > kMaxWorkerMessageSize) {
212 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
213 "maximum allowed";
214 stderr_watcher_ = nullptr;
215 return;
216 }
217 message.append(buf.begin(), buf.begin() + count);
218 }
219
220 LOG(ERROR) << worker_msg << message;
221}
222
Andreea Costinas5862b102020-03-19 14:45:36 +0100223void SandboxedWorker::OnProxyResolved(
224 const std::string& target_url,
225 bool success,
226 const std::vector<std::string>& proxy_servers) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200227 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100228 reply.set_target_url(target_url);
229
230 // Only http and direct proxies are supported at the moment.
231 for (const auto& proxy : proxy_servers) {
232 if (base::StartsWith(proxy, kPrefixHttp,
233 base::CompareCase::INSENSITIVE_ASCII) ||
234 base::StartsWith(proxy, kPrefixDirect,
235 base::CompareCase::INSENSITIVE_ASCII)) {
Andreea Costinasa89309d2020-05-08 15:51:12 +0200236 // Make sure the local proxy doesn't try to connect to itself.
237 if (proxy.find(local_proxy_host_and_port()) == std::string::npos) {
238 reply.add_proxy_servers(proxy);
239 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100240 }
241 }
242
Andreea Costinasaae97382020-05-05 13:31:58 +0200243 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100244 *configs.mutable_proxy_resolution_reply() = reply;
245
246 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
247 LOG(ERROR) << "Failed to send proxy resolution reply to worker" << pid_;
248 }
249}
250
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100251} // namespace system_proxy