blob: a61eb9ce056eb88e14b9724ecbeab7840d7553b4 [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>
18
19#include "bindings/worker_common.pb.h"
20#include "system-proxy/protobuf_util.h"
21
22namespace {
23constexpr char kSystemProxyWorkerBin[] = "/usr/sbin/system_proxy_worker";
24constexpr char kSeccompFilterPath[] =
25 "/usr/share/policy/system-proxy-worker-seccomp.policy";
26constexpr int kMaxWorkerMessageSize = 2048;
27// Size of the buffer array used to read data from the worker's stderr.
28constexpr int kWorkerBufferSize = 1024;
29
30} // namespace
31
32namespace system_proxy {
33
34SandboxedWorker::SandboxedWorker() : jail_(minijail_new()) {}
35
36void SandboxedWorker::Start() {
37 DCHECK(!IsRunning()) << "Worker is already running.";
38
39 if (!jail_)
40 return;
41
42 minijail_namespace_net(jail_.get());
43 minijail_no_new_privs(jail_.get());
44 minijail_use_seccomp_filter(jail_.get());
45 minijail_parse_seccomp_filters(jail_.get(), kSeccompFilterPath);
46
47 int child_stdin = -1, child_stdout = -1, child_stderr = -1;
48
49 std::vector<char*> args_ptr;
50
51 args_ptr.push_back(const_cast<char*>(kSystemProxyWorkerBin));
52 args_ptr.push_back(nullptr);
53
54 // Execute the command.
55 int res =
56 minijail_run_pid_pipes(jail_.get(), args_ptr[0], args_ptr.data(), &pid_,
57 &child_stdin, &child_stdout, &child_stderr);
58
59 if (res != 0) {
60 LOG(ERROR) << "Failed to start sandboxed worker: " << strerror(-res);
61 return;
62 }
63
64 // Make sure the pipes never block.
65 if (!base::SetNonBlocking(child_stdin))
66 LOG(WARNING) << "Failed to set stdin non-blocking";
67 if (!base::SetNonBlocking(child_stdout))
68 LOG(WARNING) << "Failed to set stdout non-blocking";
69 if (!base::SetNonBlocking(child_stderr))
70 LOG(WARNING) << "Failed to set stderr non-blocking";
71
72 stdin_pipe_.reset(child_stdin);
73 stdout_pipe_.reset(child_stdout);
74 stderr_pipe_.reset(child_stderr);
75
76 stdout_watcher_ = base::FileDescriptorWatcher::WatchReadable(
77 stdout_pipe_.get(),
78 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
79 base::Unretained(this)));
80
81 stderr_watcher_ = base::FileDescriptorWatcher::WatchReadable(
82 stderr_pipe_.get(),
83 base::BindRepeating(&SandboxedWorker::OnMessageReceived,
84 base::Unretained(this)));
85}
86
Andreea Costinas41e06442020-03-09 09:41:51 +010087void SandboxedWorker::SetUsernameAndPassword(const std::string& username,
88 const std::string& password) {
89 Credentials credentials;
90 credentials.set_username(username);
91 credentials.set_password(password);
92 WorkerConfigs configs;
93 *configs.mutable_credentials() = credentials;
94 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
95 LOG(ERROR) << "Failed to set credentials for worker " << pid_;
96 }
97}
98
99void SandboxedWorker::SetListeningAddress(uint32_t addr, int port) {
100 SocketAddress address;
101 address.set_addr(addr);
102 address.set_port(port);
103 WorkerConfigs configs;
104 *configs.mutable_listening_address() = address;
105
106 if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
107 LOG(ERROR) << "Failed to set local proy address for worker " +
108 std::to_string(pid_);
109 }
110}
111
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100112bool SandboxedWorker::Stop() {
113 if (is_being_terminated_)
114 return true;
115 LOG(INFO) << "Killing " << pid_;
116 is_being_terminated_ = true;
117
118 if (kill(pid_, SIGTERM) < 0) {
119 if (errno == ESRCH) {
120 // No process or group found for pid, assume already terminated.
121 return true;
122 }
123 PLOG(ERROR) << "Failed to terminate process " << pid_;
124 return false;
125 }
126 return true;
127}
128
129bool SandboxedWorker::IsRunning() {
130 return pid_ != 0 && !is_being_terminated_;
131}
132
133void SandboxedWorker::OnMessageReceived() {
134 WorkerRequest request;
135
136 if (!ReadProtobuf(stdout_pipe_.get(), &request)) {
137 LOG(ERROR) << "Failed to read request from worker " << pid_;
138 // The message is corrupted or the pipe closed, either way stop listening.
139 stdout_watcher_ = nullptr;
140 return;
141 }
142 if (request.has_log_request()) {
143 LOG(INFO) << "[worker: " << pid_ << "]" << request.log_request().message();
144 }
145}
146
147void SandboxedWorker::OnErrorReceived() {
148 std::vector<char> buf;
149 buf.resize(kWorkerBufferSize);
150
151 std::string message;
152 std::string worker_msg = "[worker: " + std::to_string(pid_) + "] ";
153
154 ssize_t count = kWorkerBufferSize;
155 ssize_t total_count = 0;
156
157 while (count == kWorkerBufferSize) {
158 count = HANDLE_EINTR(read(stderr_pipe_.get(), buf.data(), buf.size()));
159
160 if (count < 0) {
161 PLOG(ERROR) << worker_msg << "Failed to read from stdio";
162 return;
163 }
164
165 if (count == 0) {
166 if (!message.empty())
167 break; // Full message was read at the first iteration.
168
169 PLOG(INFO) << worker_msg << "Pipe closed";
170 // Stop watching, otherwise the handler will fire forever.
171 stderr_watcher_ = nullptr;
172 }
173
174 total_count += count;
175 if (total_count > kMaxWorkerMessageSize) {
176 LOG(ERROR) << "Failure to read message from woker: message size exceeds "
177 "maximum allowed";
178 stderr_watcher_ = nullptr;
179 return;
180 }
181 message.append(buf.begin(), buf.begin() + count);
182 }
183
184 LOG(ERROR) << worker_msg << message;
185}
186
187} // namespace system_proxy