Global deadline for ptrace attach instead of per process

PiperOrigin-RevId: 286196033
Change-Id: Ic456b881c18518c4b52ca051fa5c58590794da17
This commit is contained in:
Wiktor Garbacz 2019-12-18 08:23:31 -08:00 committed by Copybara-Service
parent 7125458c5d
commit e969deea33

View File

@ -550,6 +550,11 @@ bool Monitor::InitPtraceAttach() {
return false; return false;
} }
if (tasks.find(pid_) == tasks.end()) {
LOG(ERROR) << "The pid " << pid_ << " was not found in its own tasklist.";
return false;
}
// With TSYNC, we can allow threads: seccomp applies to all threads. // With TSYNC, we can allow threads: seccomp applies to all threads.
if (tasks.size() > 1) { if (tasks.size() > 1) {
@ -560,83 +565,70 @@ bool Monitor::InitPtraceAttach() {
<< "."; << ".";
} }
intptr_t ptrace_opts = std::set<int> tasks_attached;
PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | int retries = 0;
PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC | absl::Time deadline = absl::Now() + absl::Seconds(2);
PTRACE_O_TRACEEXIT | PTRACE_O_TRACESECCOMP | PTRACE_O_EXITKILL;
bool main_pid_found = false; // In some situations we allow ptrace to try again when it fails.
for (auto task : tasks) { while (!tasks.empty()) {
if (task == pid_) { std::set<int> tasks_left;
main_pid_found = true; for (int task : tasks) {
} constexpr intptr_t options =
PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK |
// In some situations we allow ptrace to try again when it fails. PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC |
bool ptrace_succeeded = false; PTRACE_O_TRACEEXIT | PTRACE_O_TRACESECCOMP | PTRACE_O_EXITKILL;
int retries = 0; int ret = ptrace(PTRACE_SEIZE, task, 0, options);
auto deadline = absl::Now() + absl::Seconds(2); if (ret != 0) {
while (absl::Now() < deadline) { if (errno == EPERM) {
int ret = ptrace(PTRACE_SEIZE, task, 0, ptrace_opts); // Sometimes when a task is exiting we can get an EPERM from ptrace.
if (ret == 0) { // Let's try again up until the timeout in this situation.
ptrace_succeeded = true; PLOG(WARNING) << "ptrace(PTRACE_SEIZE, " << task << ", "
break; << absl::StrCat("0x", absl::Hex(options))
<< "), trying again...";
tasks_left.insert(task);
continue;
}
if (errno == ESRCH) {
// A task may have exited since we captured the task list, we will
// allow things to continue after we log a warning.
PLOG(WARNING)
<< "ptrace(PTRACE_SEIZE, " << task << ", "
<< absl::StrCat("0x", absl::Hex(options))
<< ") skipping exited task. Continuing with other tasks.";
continue;
}
// Any other errno will be considered a failure.
PLOG(ERROR) << "ptrace(PTRACE_SEIZE, " << task << ", "
<< absl::StrCat("0x", absl::Hex(options)) << ") failed.";
return false;
} }
if (ret != 0 && errno == ESRCH) { tasks_attached.insert(task);
// A task may have exited since we captured the task list, we will allow
// things to continue after we log a warning.
PLOG(WARNING) << "ptrace(PTRACE_SEIZE, " << task << ", "
<< absl::StrCat("0x", absl::Hex(ptrace_opts))
<< ") skipping exited task. Continuing with other tasks.";
ptrace_succeeded = true;
break;
}
if (ret != 0 && errno == EPERM) {
// Sometimes when a task is exiting we can get an EPERM from ptrace.
// Let's try again up until the timeout in this situation.
PLOG(WARNING) << "ptrace(PTRACE_SEIZE, " << task << ", "
<< absl::StrCat("0x", absl::Hex(ptrace_opts))
<< "), trying again...";
// Exponential Backoff.
constexpr auto kInitialRetry = absl::Milliseconds(1);
constexpr auto kMaxRetry = absl::Milliseconds(20);
const auto retry_interval =
kInitialRetry * (1 << std::min(10, retries++));
absl::SleepFor(std::min(retry_interval, kMaxRetry));
continue;
}
// Any other errno will be considered a failure.
PLOG(ERROR) << "ptrace(PTRACE_SEIZE, " << task << ", "
<< absl::StrCat("0x", absl::Hex(ptrace_opts)) << ") failed.";
return false;
} }
if (!tasks_left.empty()) {
if (!ptrace_succeeded) { if (absl::Now() < deadline) {
LOG(ERROR) << "ptrace(PTRACE_SEIZE, " << task << ", " LOG(ERROR) << "Attaching to sandboxee timed out: could not attach to "
<< absl::StrCat("0x", absl::Hex(ptrace_opts)) << tasks_left.size() << " tasks";
<< ") failed after retrying until the timeout."; return false;
return false; }
// Exponential Backoff.
constexpr absl::Duration kInitialRetry = absl::Milliseconds(1);
constexpr absl::Duration kMaxRetry = absl::Milliseconds(20);
const absl::Duration retry_interval =
kInitialRetry * (1 << std::min(10, retries++));
absl::SleepFor(
std::min({retry_interval, kMaxRetry, deadline - absl::Now()}));
} }
} tasks = std::move(tasks_left);
if (!main_pid_found) {
LOG(ERROR) << "The pid " << pid_ << " was not found in its own tasklist.";
return false;
} }
// Get a list of tasks after attaching. // Get a list of tasks after attaching.
std::set<int> tasks_after; if (!sanitizer::GetListOfTasks(pid_, &tasks)) {
if (!sanitizer::GetListOfTasks(pid_, &tasks_after)) {
LOG(ERROR) << "Could not get list of tasks"; LOG(ERROR) << "Could not get list of tasks";
return false; return false;
} }
// Check that no new threads have shown up. Note: tasks_after can have fewer // Check that we attached to all the threads
// tasks than before but no new tasks can be added as they would be missing if (tasks_attached != tasks) {
// from the initial task list.
if (!std::includes(tasks.begin(), tasks.end(), tasks_after.begin(),
tasks_after.end())) {
LOG(ERROR) << "The pid " << pid_ LOG(ERROR) << "The pid " << pid_
<< " spawned new threads while we were trying to attach to it."; << " spawned new threads while we were trying to attach to it.";
return false; return false;