Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 1 | // Copyright (c) 2012 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 "cros-disks/process.h" |
| 6 | |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 7 | #include <csignal> |
| 8 | #include <memory> |
| 9 | #include <ostream> |
| 10 | #include <utility> |
| 11 | |
| 12 | #include <fcntl.h> |
| 13 | #include <sys/time.h> |
| 14 | #include <unistd.h> |
| 15 | |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 16 | #include <base/files/file_path.h> |
| 17 | #include <base/files/file_util.h> |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 18 | #include <base/files/scoped_file.h> |
| 19 | #include <base/logging.h> |
| 20 | #include <base/strings/string_piece.h> |
| 21 | #include <base/strings/stringprintf.h> |
| 22 | #include <chromeos/libminijail.h> |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 23 | |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 24 | #include <gmock/gmock.h> |
| 25 | #include <gtest/gtest.h> |
| 26 | |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 27 | #include "cros-disks/sandboxed_process.h" |
| 28 | |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 29 | namespace cros_disks { |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 30 | namespace { |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 31 | |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 32 | using testing::_; |
| 33 | using testing::Contains; |
Sergei Datsenko | 9246e9c | 2019-03-22 10:26:47 +1100 | [diff] [blame] | 34 | using testing::ElementsAre; |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 35 | using testing::PrintToStringParamName; |
Sergei Datsenko | 9246e9c | 2019-03-22 10:26:47 +1100 | [diff] [blame] | 36 | using testing::Return; |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 37 | using testing::SizeIs; |
| 38 | using testing::StartsWith; |
| 39 | using testing::UnorderedElementsAre; |
| 40 | using testing::Values; |
Sergei Datsenko | 9246e9c | 2019-03-22 10:26:47 +1100 | [diff] [blame] | 41 | |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 42 | // Sets a signal handler for SIGALRM and an interval timer signaling SIGALRM at |
| 43 | // regular intervals. |
| 44 | class AlarmGuard { |
| 45 | public: |
| 46 | explicit AlarmGuard(const int timer_interval_ms) { |
| 47 | CHECK(!old_handler_); |
| 48 | count_ = 0; |
| 49 | old_handler_ = signal(SIGALRM, &Handler); |
| 50 | CHECK_NE(old_handler_, SIG_ERR); |
| 51 | SetIntervalTimer(timer_interval_ms * 1000 /* microseconds */); |
| 52 | } |
| 53 | |
| 54 | ~AlarmGuard() { |
| 55 | SetIntervalTimer(0); |
| 56 | CHECK_EQ(signal(SIGALRM, old_handler_), &Handler); |
| 57 | old_handler_ = nullptr; |
| 58 | } |
| 59 | |
| 60 | // Number of times SIGALRM has been received. |
| 61 | static int count() { return count_; } |
| 62 | |
| 63 | private: |
| 64 | static void Handler(int sig) { |
| 65 | CHECK_EQ(sig, SIGALRM); |
| 66 | ++count_; |
| 67 | } |
| 68 | |
| 69 | static void SetIntervalTimer(const int usec) { |
| 70 | const itimerval tv = {{0, usec}, {0, usec}}; |
| 71 | if (setitimer(ITIMER_REAL, &tv, nullptr) < 0) { |
| 72 | PLOG(FATAL) << "Cannot set timer"; |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | // Number of times SIGALRM has been received. |
| 77 | static int count_; |
| 78 | |
| 79 | using SigHandler = void (*)(int); |
| 80 | static SigHandler old_handler_; |
| 81 | |
| 82 | DISALLOW_COPY_AND_ASSIGN(AlarmGuard); |
| 83 | }; |
| 84 | |
| 85 | int AlarmGuard::count_ = 0; |
| 86 | AlarmGuard::SigHandler AlarmGuard::old_handler_ = nullptr; |
| 87 | |
| 88 | // Anonymous pipe. |
| 89 | struct Pipe { |
| 90 | base::ScopedFD read_fd, write_fd; |
| 91 | |
| 92 | // Creates an open pipe. |
| 93 | Pipe() { |
| 94 | int fds[2]; |
| 95 | if (pipe(fds) < 0) { |
| 96 | PLOG(FATAL) << "Cannot create pipe "; |
| 97 | } |
| 98 | |
| 99 | read_fd.reset(fds[0]); |
| 100 | write_fd.reset(fds[1]); |
| 101 | } |
| 102 | }; |
| 103 | |
| 104 | std::string Read(const base::ScopedFD fd) { |
| 105 | char buffer[PIPE_BUF]; |
| 106 | |
| 107 | LOG(INFO) << "Reading up to " << PIPE_BUF << " bytes from fd " << fd.get() |
| 108 | << "..."; |
| 109 | const ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, PIPE_BUF)); |
| 110 | PLOG_IF(FATAL, bytes_read < 0) |
| 111 | << "Cannot read from file descriptor " << fd.get(); |
| 112 | |
| 113 | LOG(INFO) << "Read " << bytes_read << " bytes from fd " << fd.get(); |
| 114 | return std::string(buffer, bytes_read); |
| 115 | } |
| 116 | |
| 117 | void WriteAndClose(const base::ScopedFD fd, base::StringPiece s) { |
| 118 | while (!s.empty()) { |
| 119 | const ssize_t bytes_written = |
| 120 | HANDLE_EINTR(write(fd.get(), s.data(), s.size())); |
| 121 | PLOG_IF(FATAL, bytes_written < 0) |
| 122 | << "Cannot write to file descriptor " << fd.get(); |
| 123 | |
| 124 | s.remove_prefix(bytes_written); |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | // A mock Process class for testing the Process base class. |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 129 | class ProcessUnderTest : public Process { |
| 130 | public: |
Ben Chan | 21150af | 2019-09-11 17:04:07 -0700 | [diff] [blame] | 131 | MOCK_METHOD(pid_t, |
| 132 | StartImpl, |
| 133 | (base::ScopedFD*, base::ScopedFD*, base::ScopedFD*), |
| 134 | (override)); |
| 135 | MOCK_METHOD(int, WaitImpl, (), (override)); |
| 136 | MOCK_METHOD(bool, WaitNonBlockingImpl, (int*), (override)); |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 137 | }; |
| 138 | |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 139 | struct ProcessFactory { |
| 140 | base::StringPiece name; |
| 141 | std::unique_ptr<Process> (*make_process)(); |
| 142 | }; |
| 143 | |
| 144 | std::ostream& operator<<(std::ostream& out, const ProcessFactory& x) { |
| 145 | return out << x.name; |
| 146 | } |
| 147 | |
| 148 | } // namespace |
| 149 | |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 150 | class ProcessTest : public ::testing::Test { |
| 151 | protected: |
| 152 | ProcessUnderTest process_; |
| 153 | }; |
| 154 | |
| 155 | TEST_F(ProcessTest, GetArguments) { |
François Degros | 5593b8c | 2019-07-25 12:27:42 +1000 | [diff] [blame] | 156 | const char* const kTestArguments[] = {"/bin/ls", "-l", "", "."}; |
Ben Chan | 6057fe6 | 2016-12-02 10:08:59 -0800 | [diff] [blame] | 157 | for (const char* test_argument : kTestArguments) { |
| 158 | process_.AddArgument(test_argument); |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 159 | } |
| 160 | |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 161 | EXPECT_THAT(process_.arguments(), ElementsAre("/bin/ls", "-l", "", ".")); |
Sergei Datsenko | 9246e9c | 2019-03-22 10:26:47 +1100 | [diff] [blame] | 162 | |
François Degros | 5593b8c | 2019-07-25 12:27:42 +1000 | [diff] [blame] | 163 | char* const* arguments = process_.GetArguments(); |
Ben Chan | 44e7ea6 | 2014-08-29 18:13:37 -0700 | [diff] [blame] | 164 | EXPECT_NE(nullptr, arguments); |
Ben Chan | 6057fe6 | 2016-12-02 10:08:59 -0800 | [diff] [blame] | 165 | for (const char* test_argument : kTestArguments) { |
| 166 | EXPECT_STREQ(test_argument, *arguments); |
| 167 | ++arguments; |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 168 | } |
Ben Chan | 6057fe6 | 2016-12-02 10:08:59 -0800 | [diff] [blame] | 169 | EXPECT_EQ(nullptr, *arguments); |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 170 | } |
| 171 | |
| 172 | TEST_F(ProcessTest, GetArgumentsWithNoArgumentsAdded) { |
Ben Chan | 44e7ea6 | 2014-08-29 18:13:37 -0700 | [diff] [blame] | 173 | EXPECT_EQ(nullptr, process_.GetArguments()); |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 174 | } |
| 175 | |
Sergei Datsenko | 9246e9c | 2019-03-22 10:26:47 +1100 | [diff] [blame] | 176 | TEST_F(ProcessTest, Run_Success) { |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 177 | process_.AddArgument("foo"); |
François Degros | 5593b8c | 2019-07-25 12:27:42 +1000 | [diff] [blame] | 178 | EXPECT_CALL(process_, StartImpl(_, _, _)).WillOnce(Return(123)); |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 179 | EXPECT_CALL(process_, WaitImpl()).WillOnce(Return(42)); |
| 180 | EXPECT_CALL(process_, WaitNonBlockingImpl(_)).Times(0); |
Sergei Datsenko | 9246e9c | 2019-03-22 10:26:47 +1100 | [diff] [blame] | 181 | EXPECT_EQ(42, process_.Run()); |
| 182 | } |
| 183 | |
| 184 | TEST_F(ProcessTest, Run_Fail) { |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 185 | process_.AddArgument("foo"); |
François Degros | 5593b8c | 2019-07-25 12:27:42 +1000 | [diff] [blame] | 186 | EXPECT_CALL(process_, StartImpl(_, _, _)).WillOnce(Return(-1)); |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 187 | EXPECT_CALL(process_, WaitImpl()).Times(0); |
| 188 | EXPECT_CALL(process_, WaitNonBlockingImpl(_)).Times(0); |
Sergei Datsenko | 9246e9c | 2019-03-22 10:26:47 +1100 | [diff] [blame] | 189 | EXPECT_EQ(-1, process_.Run()); |
| 190 | } |
| 191 | |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 192 | class ProcessRunTest : public ::testing::TestWithParam<ProcessFactory> { |
| 193 | public: |
| 194 | ProcessRunTest() { |
| 195 | // Ensure that we get an error message if Minijail crashes. |
| 196 | // TODO(crbug.com/1007098) Remove the following line or this comment |
| 197 | // depending on how this bug is resolved. |
| 198 | minijail_log_to_fd(STDERR_FILENO, 0); |
| 199 | } |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 200 | |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 201 | const std::unique_ptr<Process> process_ = GetParam().make_process(); |
| 202 | }; |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 203 | |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 204 | TEST_P(ProcessRunTest, ReturnsZero) { |
| 205 | Process& process = *process_; |
| 206 | process.AddArgument("/bin/true"); |
| 207 | EXPECT_EQ(process.Run(), 0); |
Sergei Datsenko | cd676b7 | 2019-05-10 11:42:05 +1000 | [diff] [blame] | 208 | } |
| 209 | |
François Degros | c0b33b1 | 2019-09-12 14:50:43 +1000 | [diff] [blame^] | 210 | TEST_P(ProcessRunTest, ReturnsNonZero) { |
| 211 | Process& process = *process_; |
| 212 | process.AddArgument("/bin/false"); |
| 213 | EXPECT_EQ(process.Run(), 1); |
| 214 | } |
| 215 | |
| 216 | TEST_P(ProcessRunTest, KilledBySigKill) { |
| 217 | Process& process = *process_; |
| 218 | process.AddArgument("/bin/sh"); |
| 219 | process.AddArgument("-c"); |
| 220 | process.AddArgument("kill -KILL $$; sleep 1000"); |
| 221 | EXPECT_EQ(process.Run(), 128 + SIGKILL); |
| 222 | } |
| 223 | |
| 224 | TEST_P(ProcessRunTest, KilledBySigSys) { |
| 225 | Process& process = *process_; |
| 226 | process.AddArgument("/bin/sh"); |
| 227 | process.AddArgument("-c"); |
| 228 | process.AddArgument("kill -SYS $$; sleep 1000"); |
| 229 | EXPECT_EQ(process.Run(), MINIJAIL_ERR_JAIL); |
| 230 | } |
| 231 | |
| 232 | TEST_P(ProcessRunTest, CannotExec) { |
| 233 | Process& process = *process_; |
| 234 | process.AddArgument("non_existing_exe_foo_bar"); |
| 235 | // SandboxedProcess returns 255, but it isn't explicitly specified. |
| 236 | EXPECT_GT(process.Run(), 0); |
| 237 | } |
| 238 | |
| 239 | TEST_P(ProcessRunTest, CapturesInterleavedOutputs) { |
| 240 | Process& process = *process_; |
| 241 | process.AddArgument("/bin/sh"); |
| 242 | process.AddArgument("-c"); |
| 243 | process.AddArgument(R"( |
| 244 | printf 'Line 1\nLine ' >&1; |
| 245 | printf 'Line 2\nLine' >&2; |
| 246 | printf '3\nLine 4\n' >&1; |
| 247 | printf ' 5\nLine 6' >&2; |
| 248 | )"); |
| 249 | |
| 250 | std::vector<std::string> output; |
| 251 | EXPECT_EQ(process.Run(&output), 0); |
| 252 | EXPECT_THAT(output, UnorderedElementsAre("OUT: Line 1", "OUT: Line 3", |
| 253 | "OUT: Line 4", "ERR: Line 2", |
| 254 | "ERR: Line 5", "ERR: Line 6")); |
| 255 | } |
| 256 | |
| 257 | TEST_P(ProcessRunTest, CapturesLotsOfOutputData) { |
| 258 | Process& process = *process_; |
| 259 | process.AddArgument("/bin/sh"); |
| 260 | process.AddArgument("-c"); |
| 261 | process.AddArgument(R"( |
| 262 | for i in $(seq 1 1000); do |
| 263 | printf 'Message %i\n' $i >&1; |
| 264 | printf 'Error %i\n' $i >&2; |
| 265 | done; |
| 266 | )"); |
| 267 | |
| 268 | std::vector<std::string> output; |
| 269 | EXPECT_EQ(process.Run(&output), 0); |
| 270 | EXPECT_THAT(output, SizeIs(2000)); |
| 271 | } |
| 272 | |
| 273 | TEST_P(ProcessRunTest, DoesNotBlockWhenReadingFromStdIn) { |
| 274 | Process& process = *process_; |
| 275 | process.AddArgument("/bin/cat"); |
| 276 | |
| 277 | // By default, /bin/cat reads from stdin. If the pipe connected to stdin was |
| 278 | // left open, the process would block indefinitely while reading from it. |
| 279 | EXPECT_EQ(process.Run(), 0); |
| 280 | } |
| 281 | |
| 282 | TEST_P(ProcessRunTest, DoesNotWaitForBackgroundProcessToFinish) { |
| 283 | Process& process = *process_; |
| 284 | process.AddArgument("/bin/sh"); |
| 285 | process.AddArgument("-c"); |
| 286 | |
| 287 | // Pipe to unblock the background process and allow it to finish. |
| 288 | Pipe p1; |
| 289 | ASSERT_EQ(fcntl(p1.write_fd.get(), F_SETFD, FD_CLOEXEC), 0); |
| 290 | // Pipe to monitor the background process and wait for it to finish. |
| 291 | Pipe p2; |
| 292 | ASSERT_EQ(fcntl(p2.read_fd.get(), F_SETFD, FD_CLOEXEC), 0); |
| 293 | |
| 294 | process.AddArgument(base::StringPrintf(R"( |
| 295 | printf 'Begin\n'; |
| 296 | ( |
| 297 | exec 0<&-; |
| 298 | exec 1>&-; |
| 299 | exec 2>&-; |
| 300 | read line <&%d; |
| 301 | printf '%%s and End' "$line" >&%d; |
| 302 | exit 42; |
| 303 | )& |
| 304 | printf 'Started background process %%i\n' $! |
| 305 | exit 5; |
| 306 | )", |
| 307 | p1.read_fd.get(), p2.write_fd.get())); |
| 308 | |
| 309 | std::vector<std::string> output; |
| 310 | EXPECT_EQ(process.Run(&output), 5); |
| 311 | EXPECT_THAT( |
| 312 | output, |
| 313 | ElementsAre("OUT: Begin", StartsWith("OUT: Started background process"))); |
| 314 | |
| 315 | // Unblock the orphaned background process. |
| 316 | LOG(INFO) << "Unblocking background process"; |
| 317 | WriteAndClose(std::move(p1.write_fd), "Continue\n"); |
| 318 | |
| 319 | // Wait for the orphaned background processes to finish. If the main test |
| 320 | // program finishes before these background processes, the test framework |
| 321 | // complains about leaked processes. |
| 322 | p2.write_fd.reset(); |
| 323 | LOG(INFO) << "Waiting for background process to finish"; |
| 324 | EXPECT_EQ(Read(std::move(p2.read_fd)), "Continue and End"); |
| 325 | } |
| 326 | |
| 327 | TEST_P(ProcessRunTest, UndisturbedBySignalsWhenWaiting) { |
| 328 | Process& process = *process_; |
| 329 | process.AddArgument("/bin/sh"); |
| 330 | process.AddArgument("-c"); |
| 331 | process.AddArgument(R"( |
| 332 | sleep 1; |
| 333 | exit 42; |
| 334 | )"); |
| 335 | |
| 336 | // Activate an interval timer. |
| 337 | const AlarmGuard guard(13 /* milliseconds */); |
| 338 | EXPECT_TRUE(process.Start()); |
| 339 | EXPECT_EQ(process.Wait(), 42); |
| 340 | EXPECT_GT(AlarmGuard::count(), 0); |
| 341 | } |
| 342 | |
| 343 | INSTANTIATE_TEST_SUITE_P(ProcessRun, |
| 344 | ProcessRunTest, |
| 345 | Values(ProcessFactory{ |
| 346 | "SandboxedProcess", |
| 347 | []() -> std::unique_ptr<Process> { |
| 348 | return std::make_unique<SandboxedProcess>(); |
| 349 | }}), |
| 350 | PrintToStringParamName()); |
| 351 | |
Ben Chan | 6f391cb | 2012-03-21 17:38:21 -0700 | [diff] [blame] | 352 | } // namespace cros_disks |