blob: a86f11f5163049d5f7a118e4adace70ddb670131 [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 Costinas5862b102020-03-19 14:45:36 +010019#include <brillo/http/http_transport.h>
20#include <google/protobuf/repeated_field.h>
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010021
22#include "bindings/worker_common.pb.h"
23#include "system-proxy/protobuf_util.h"
Andreea Costinas5862b102020-03-19 14:45:36 +010024#include "system-proxy/system_proxy_adaptor.h"
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010025
26namespace {
27constexpr char kSystemProxyWorkerBin[] = "/usr/sbin/system_proxy_worker";
28constexpr char kSeccompFilterPath[] =
29 "/usr/share/policy/system-proxy-worker-seccomp.policy";
30constexpr int kMaxWorkerMessageSize = 2048;
31// Size of the buffer array used to read data from the worker's stderr.
32constexpr int kWorkerBufferSize = 1024;
Andreea Costinas5862b102020-03-19 14:45:36 +010033constexpr char kPrefixDirect[] = "direct://";
34constexpr char kPrefixHttp[] = "http://";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010035} // namespace
36
37namespace system_proxy {
38
Andreea Costinas5862b102020-03-19 14:45:36 +010039SandboxedWorker::SandboxedWorker(base::WeakPtr<SystemProxyAdaptor> adaptor)
Andreea Costinas396c1de2020-04-12 23:44:46 +020040 : jail_(minijail_new()), adaptor_(adaptor), pid_(0) {}
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010041
Andreea Costinasc9defae2020-04-22 10:28:35 +020042bool SandboxedWorker::Start() {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010043 DCHECK(!IsRunning()) << "Worker is already running.";
44
45 if (!jail_)
Andreea Costinasc9defae2020-04-22 10:28:35 +020046 return false;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010047
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020048 minijail_namespace_pids(jail_.get());
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010049 minijail_namespace_net(jail_.get());
50 minijail_no_new_privs(jail_.get());
51 minijail_use_seccomp_filter(jail_.get());
52 minijail_parse_seccomp_filters(jail_.get(), kSeccompFilterPath);
53
54 int child_stdin = -1, child_stdout = -1, child_stderr = -1;
55
56 std::vector<char*> args_ptr;
57
58 args_ptr.push_back(const_cast<char*>(kSystemProxyWorkerBin));
59 args_ptr.push_back(nullptr);
60
61 // Execute the command.
62 int res =
63 minijail_run_pid_pipes(jail_.get(), args_ptr[0], args_ptr.data(), &pid_,
64 &child_stdin, &child_stdout, &child_stderr);
65
66 if (res != 0) {
67 LOG(ERROR) << "Failed to start sandboxed worker: " << strerror(-res);
Andreea Costinasc9defae2020-04-22 10:28:35 +020068 return false;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010069 }
70
71 // Make sure the pipes never block.
72 if (!base::SetNonBlocking(child_stdin))
73 LOG(WARNING) << "Failed to set stdin non-blocking";
74 if (!base::SetNonBlocking(child_stdout))
75 LOG(WARNING) << "Failed to set stdout non-blocking";
76 if (!base::SetNonBlocking(child_stderr))
77 LOG(WARNING) << "Failed to set stderr non-blocking";
78
79 stdin_pipe_.reset(child_stdin);
80 stdout_pipe_.reset(child_stdout);
81 stderr_pipe_.reset(child_stderr);
82
83 stdout_watcher_ = base::FileDescriptorWatcher::WatchReadable(
84 stdout_pipe_.get(),
85 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
86 base::Unretained(this)));
87
88 stderr_watcher_ = base::FileDescriptorWatcher::WatchReadable(
Andreea Costinasc9defae2020-04-22 10:28:35 +020089 stderr_pipe_.get(), base::BindRepeating(&SandboxedWorker::OnErrorReceived,
90 base::Unretained(this)));
91 return true;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010092}
93
Andreea Costinas41e06442020-03-09 09:41:51 +010094void SandboxedWorker::SetUsernameAndPassword(const std::string& username,
95 const std::string& password) {
Andreea Costinasaae97382020-05-05 13:31:58 +020096 worker::Credentials credentials;
Andreea Costinas41e06442020-03-09 09:41:51 +010097 credentials.set_username(username);
98 credentials.set_password(password);
Andreea Costinasaae97382020-05-05 13:31:58 +020099 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100100 *configs.mutable_credentials() = credentials;
101 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
102 LOG(ERROR) << "Failed to set credentials for worker " << pid_;
103 }
104}
105
Andreea Costinasc9defae2020-04-22 10:28:35 +0200106bool SandboxedWorker::SetListeningAddress(uint32_t addr, int port) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200107 worker::SocketAddress address;
Andreea Costinas41e06442020-03-09 09:41:51 +0100108 address.set_addr(addr);
109 address.set_port(port);
Andreea Costinasaae97382020-05-05 13:31:58 +0200110 worker::WorkerConfigs configs;
Andreea Costinas41e06442020-03-09 09:41:51 +0100111 *configs.mutable_listening_address() = address;
112
113 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
Andreea Costinasc9defae2020-04-22 10:28:35 +0200114 LOG(ERROR) << "Failed to set local proxy address for worker " << pid_;
115 return false;
Andreea Costinas41e06442020-03-09 09:41:51 +0100116 }
Andreea Costinasc9defae2020-04-22 10:28:35 +0200117 return true;
Andreea Costinas41e06442020-03-09 09:41:51 +0100118}
119
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100120bool SandboxedWorker::Stop() {
121 if (is_being_terminated_)
122 return true;
123 LOG(INFO) << "Killing " << pid_;
124 is_being_terminated_ = true;
125
126 if (kill(pid_, SIGTERM) < 0) {
127 if (errno == ESRCH) {
128 // No process or group found for pid, assume already terminated.
129 return true;
130 }
131 PLOG(ERROR) << "Failed to terminate process " << pid_;
132 return false;
133 }
134 return true;
135}
136
137bool SandboxedWorker::IsRunning() {
138 return pid_ != 0 && !is_being_terminated_;
139}
140
141void SandboxedWorker::OnMessageReceived() {
Andreea Costinasaae97382020-05-05 13:31:58 +0200142 worker::WorkerRequest request;
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100143
144 if (!ReadProtobuf(stdout_pipe_.get(), &request)) {
145 LOG(ERROR) << "Failed to read request from worker " << pid_;
146 // The message is corrupted or the pipe closed, either way stop listening.
147 stdout_watcher_ = nullptr;
148 return;
149 }
150 if (request.has_log_request()) {
151 LOG(INFO) << "[worker: " << pid_ << "]" << request.log_request().message();
152 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100153
154 if (request.has_proxy_resolution_request()) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200155 const worker::ProxyResolutionRequest& proxy_request =
Andreea Costinas5862b102020-03-19 14:45:36 +0100156 request.proxy_resolution_request();
157
158 // This callback will always be called with at least one proxy entry. Even
159 // if the dbus call itself fails, the proxy server list will contain the
160 // direct proxy.
161 adaptor_->GetChromeProxyServersAsync(
162 proxy_request.target_url(),
163 base::BindRepeating(&SandboxedWorker::OnProxyResolved,
164 weak_ptr_factory_.GetWeakPtr(),
165 proxy_request.target_url()));
166 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100167}
168
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200169void SandboxedWorker::SetNetNamespaceLifelineFd(
170 base::ScopedFD net_namespace_lifeline_fd) {
171 // Sanity check that only one network namespace is setup for the worker
172 // process.
173 DCHECK(!net_namespace_lifeline_fd.is_valid());
174 net_namespace_lifeline_fd_ = std::move(net_namespace_lifeline_fd);
175}
176
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100177void SandboxedWorker::OnErrorReceived() {
178 std::vector<char> buf;
179 buf.resize(kWorkerBufferSize);
180
181 std::string message;
182 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
183
184 ssize_t count = kWorkerBufferSize;
185 ssize_t total_count = 0;
186
187 while (count == kWorkerBufferSize) {
188 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
189
190 if (count < 0) {
191 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
192 return;
193 }
194
195 if (count == 0) {
196 if (!message.empty())
197 break; // Full message was read at the first iteration.
198
199 PLOG(INFO) << worker_msg << "Pipe closed";
200 // Stop watching, otherwise the handler will fire forever.
201 stderr_watcher_ = nullptr;
202 }
203
204 total_count += count;
205 if (total_count > kMaxWorkerMessageSize) {
206 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
207 "maximum allowed";
208 stderr_watcher_ = nullptr;
209 return;
210 }
211 message.append(buf.begin(), buf.begin() + count);
212 }
213
214 LOG(ERROR) << worker_msg << message;
215}
216
Andreea Costinas5862b102020-03-19 14:45:36 +0100217void SandboxedWorker::OnProxyResolved(
218 const std::string& target_url,
219 bool success,
220 const std::vector<std::string>& proxy_servers) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200221 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100222 reply.set_target_url(target_url);
223
224 // Only http and direct proxies are supported at the moment.
225 for (const auto& proxy : proxy_servers) {
226 if (base::StartsWith(proxy, kPrefixHttp,
227 base::CompareCase::INSENSITIVE_ASCII) ||
228 base::StartsWith(proxy, kPrefixDirect,
229 base::CompareCase::INSENSITIVE_ASCII)) {
230 reply.add_proxy_servers(proxy);
231 }
232 }
233
Andreea Costinasaae97382020-05-05 13:31:58 +0200234 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100235 *configs.mutable_proxy_resolution_reply() = reply;
236
237 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
238 LOG(ERROR) << "Failed to send proxy resolution reply to worker" << pid_;
239 }
240}
241
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100242} // namespace system_proxy