blob: 2ed6ed9a2586ba0a92a9b9d3e1be5ed037ce49ad [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>
12#include <vector>
13
14#include <base/bind.h>
15#include <base/callback_helpers.h>
16#include <base/files/file_util.h>
17#include <base/strings/string_util.h>
Andreea Costinas5862b102020-03-19 14:45:36 +010018#include <brillo/http/http_transport.h>
19#include <google/protobuf/repeated_field.h>
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010020
21#include "bindings/worker_common.pb.h"
22#include "system-proxy/protobuf_util.h"
Andreea Costinas5862b102020-03-19 14:45:36 +010023#include "system-proxy/system_proxy_adaptor.h"
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010024
25namespace {
26constexpr char kSystemProxyWorkerBin[] = "/usr/sbin/system_proxy_worker";
27constexpr char kSeccompFilterPath[] =
28 "/usr/share/policy/system-proxy-worker-seccomp.policy";
29constexpr int kMaxWorkerMessageSize = 2048;
30// Size of the buffer array used to read data from the worker's stderr.
31constexpr int kWorkerBufferSize = 1024;
Andreea Costinas5862b102020-03-19 14:45:36 +010032constexpr char kPrefixDirect[] = "direct://";
33constexpr char kPrefixHttp[] = "http://";
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010034} // namespace
35
36namespace system_proxy {
37
Andreea Costinas5862b102020-03-19 14:45:36 +010038SandboxedWorker::SandboxedWorker(base::WeakPtr<SystemProxyAdaptor> adaptor)
39 : jail_(minijail_new()), adaptor_(adaptor) {}
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010040
41void SandboxedWorker::Start() {
42 DCHECK(!IsRunning()) << "Worker is already running.";
43
44 if (!jail_)
45 return;
46
47 minijail_namespace_net(jail_.get());
48 minijail_no_new_privs(jail_.get());
49 minijail_use_seccomp_filter(jail_.get());
50 minijail_parse_seccomp_filters(jail_.get(), kSeccompFilterPath);
51
52 int child_stdin = -1, child_stdout = -1, child_stderr = -1;
53
54 std::vector<char*> args_ptr;
55
56 args_ptr.push_back(const_cast<char*>(kSystemProxyWorkerBin));
57 args_ptr.push_back(nullptr);
58
59 // Execute the command.
60 int res =
61 minijail_run_pid_pipes(jail_.get(), args_ptr[0], args_ptr.data(), &pid_,
62 &child_stdin, &child_stdout, &child_stderr);
63
64 if (res != 0) {
65 LOG(ERROR) << "Failed to start sandboxed worker: " << strerror(-res);
66 return;
67 }
68
69 // Make sure the pipes never block.
70 if (!base::SetNonBlocking(child_stdin))
71 LOG(WARNING) << "Failed to set stdin non-blocking";
72 if (!base::SetNonBlocking(child_stdout))
73 LOG(WARNING) << "Failed to set stdout non-blocking";
74 if (!base::SetNonBlocking(child_stderr))
75 LOG(WARNING) << "Failed to set stderr non-blocking";
76
77 stdin_pipe_.reset(child_stdin);
78 stdout_pipe_.reset(child_stdout);
79 stderr_pipe_.reset(child_stderr);
80
81 stdout_watcher_ = base::FileDescriptorWatcher::WatchReadable(
82 stdout_pipe_.get(),
83 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
84 base::Unretained(this)));
85
86 stderr_watcher_ = base::FileDescriptorWatcher::WatchReadable(
87 stderr_pipe_.get(),
88 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
89 base::Unretained(this)));
90}
91
Andreea Costinas41e06442020-03-09 09:41:51 +010092void SandboxedWorker::SetUsernameAndPassword(const std::string& username,
93 const std::string& password) {
94 Credentials credentials;
95 credentials.set_username(username);
96 credentials.set_password(password);
97 WorkerConfigs configs;
98 *configs.mutable_credentials() = credentials;
99 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
100 LOG(ERROR) << "Failed to set credentials for worker " << pid_;
101 }
102}
103
104void SandboxedWorker::SetListeningAddress(uint32_t addr, int port) {
105 SocketAddress address;
106 address.set_addr(addr);
107 address.set_port(port);
108 WorkerConfigs configs;
109 *configs.mutable_listening_address() = address;
110
111 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
Andreea Costinas5862b102020-03-19 14:45:36 +0100112 LOG(ERROR) << "Failed to set local proy address for worker " << pid_;
Andreea Costinas41e06442020-03-09 09:41:51 +0100113 }
114}
115
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100116bool SandboxedWorker::Stop() {
117 if (is_being_terminated_)
118 return true;
119 LOG(INFO) << "Killing " << pid_;
120 is_being_terminated_ = true;
121
122 if (kill(pid_, SIGTERM) < 0) {
123 if (errno == ESRCH) {
124 // No process or group found for pid, assume already terminated.
125 return true;
126 }
127 PLOG(ERROR) << "Failed to terminate process " << pid_;
128 return false;
129 }
130 return true;
131}
132
133bool SandboxedWorker::IsRunning() {
134 return pid_ != 0 && !is_being_terminated_;
135}
136
137void SandboxedWorker::OnMessageReceived() {
138 WorkerRequest request;
139
140 if (!ReadProtobuf(stdout_pipe_.get(), &request)) {
141 LOG(ERROR) << "Failed to read request from worker " << pid_;
142 // The message is corrupted or the pipe closed, either way stop listening.
143 stdout_watcher_ = nullptr;
144 return;
145 }
146 if (request.has_log_request()) {
147 LOG(INFO) << "[worker: " << pid_ << "]" << request.log_request().message();
148 }
Andreea Costinas5862b102020-03-19 14:45:36 +0100149
150 if (request.has_proxy_resolution_request()) {
151 const ProxyResolutionRequest& proxy_request =
152 request.proxy_resolution_request();
153
154 // This callback will always be called with at least one proxy entry. Even
155 // if the dbus call itself fails, the proxy server list will contain the
156 // direct proxy.
157 adaptor_->GetChromeProxyServersAsync(
158 proxy_request.target_url(),
159 base::BindRepeating(&SandboxedWorker::OnProxyResolved,
160 weak_ptr_factory_.GetWeakPtr(),
161 proxy_request.target_url()));
162 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100163}
164
165void SandboxedWorker::OnErrorReceived() {
166 std::vector<char> buf;
167 buf.resize(kWorkerBufferSize);
168
169 std::string message;
170 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
171
172 ssize_t count = kWorkerBufferSize;
173 ssize_t total_count = 0;
174
175 while (count == kWorkerBufferSize) {
176 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
177
178 if (count < 0) {
179 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
180 return;
181 }
182
183 if (count == 0) {
184 if (!message.empty())
185 break; // Full message was read at the first iteration.
186
187 PLOG(INFO) << worker_msg << "Pipe closed";
188 // Stop watching, otherwise the handler will fire forever.
189 stderr_watcher_ = nullptr;
190 }
191
192 total_count += count;
193 if (total_count > kMaxWorkerMessageSize) {
194 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
195 "maximum allowed";
196 stderr_watcher_ = nullptr;
197 return;
198 }
199 message.append(buf.begin(), buf.begin() + count);
200 }
201
202 LOG(ERROR) << worker_msg << message;
203}
204
Andreea Costinas5862b102020-03-19 14:45:36 +0100205void SandboxedWorker::OnProxyResolved(
206 const std::string& target_url,
207 bool success,
208 const std::vector<std::string>& proxy_servers) {
209 ProxyResolutionReply reply;
210 reply.set_target_url(target_url);
211
212 // Only http and direct proxies are supported at the moment.
213 for (const auto& proxy : proxy_servers) {
214 if (base::StartsWith(proxy, kPrefixHttp,
215 base::CompareCase::INSENSITIVE_ASCII) ||
216 base::StartsWith(proxy, kPrefixDirect,
217 base::CompareCase::INSENSITIVE_ASCII)) {
218 reply.add_proxy_servers(proxy);
219 }
220 }
221
222 WorkerConfigs configs;
223 *configs.mutable_proxy_resolution_reply() = reply;
224
225 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
226 LOG(ERROR) << "Failed to send proxy resolution reply to worker" << pid_;
227 }
228}
229
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100230} // namespace system_proxy