Commit 17122f17 authored by Etienne Pierre-doray's avatar Etienne Pierre-doray Committed by Commit Bot

[Jobs API]: Fix use-after-free.

It's possible for a thread to re-enqueue a JobTaskSource right before
the job is canceled/joined and will sill end up in the queue (the main thread
may try to remove it before it's added again).
The fix is simply to avoid calling user callback if the job has the cancel
bit set. In addition, a job that's joined needs to be marked as canceled.

Bug: 1120686
Change-Id: I1ca9458a22eda71c400cfb6f0ef42efc85e38e86
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2369435Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800730}
parent 5dadc60b
...@@ -97,9 +97,6 @@ void JobHandle::Join() { ...@@ -97,9 +97,6 @@ void JobHandle::Join() {
bool must_run = task_source_->WillJoin(); bool must_run = task_source_->WillJoin();
while (must_run) while (must_run)
must_run = task_source_->RunJoinTask(); must_run = task_source_->RunJoinTask();
// Remove |task_source_| from the ThreadPool to prevent access to
// |max_concurrency_callback| after Join().
task_source_->delegate()->RemoveJobTaskSource(task_source_);
task_source_ = nullptr; task_source_ = nullptr;
} }
......
...@@ -194,19 +194,14 @@ bool JobTaskSource::WaitForParticipationOpportunity() { ...@@ -194,19 +194,14 @@ bool JobTaskSource::WaitForParticipationOpportunity() {
DCHECK_EQ(state.worker_count(), 1U); DCHECK_EQ(state.worker_count(), 1U);
DCHECK(state.is_canceled() || max_concurrency == 0U); DCHECK(state.is_canceled() || max_concurrency == 0U);
state_.DecrementWorkerCount(); state_.DecrementWorkerCount();
// Prevent subsequent accesses to user callbacks.
state_.Cancel();
return false; return false;
} }
TaskSource::RunStatus JobTaskSource::WillRunTask() { TaskSource::RunStatus JobTaskSource::WillRunTask() {
CheckedAutoLock auto_lock(worker_lock_); CheckedAutoLock auto_lock(worker_lock_);
auto state_before_add = state_.Load(); auto state_before_add = state_.Load();
const size_t max_concurrency =
GetMaxConcurrency(state_before_add.worker_count());
if (!state_before_add.is_canceled() &&
state_before_add.worker_count() < max_concurrency) {
state_before_add = state_.IncrementWorkerCount();
}
// Don't allow this worker to run the task if either: // Don't allow this worker to run the task if either:
// A) |state_| was canceled. // A) |state_| was canceled.
...@@ -215,6 +210,11 @@ TaskSource::RunStatus JobTaskSource::WillRunTask() { ...@@ -215,6 +210,11 @@ TaskSource::RunStatus JobTaskSource::WillRunTask() {
// Case A: // Case A:
if (state_before_add.is_canceled()) if (state_before_add.is_canceled())
return RunStatus::kDisallowed; return RunStatus::kDisallowed;
const size_t max_concurrency =
GetMaxConcurrency(state_before_add.worker_count());
if (state_before_add.worker_count() < max_concurrency)
state_before_add = state_.IncrementWorkerCount();
const size_t worker_count_before_add = state_before_add.worker_count(); const size_t worker_count_before_add = state_before_add.worker_count();
// Case B) or C): // Case B) or C):
if (worker_count_before_add >= max_concurrency) if (worker_count_before_add >= max_concurrency)
...@@ -230,9 +230,11 @@ size_t JobTaskSource::GetRemainingConcurrency() const { ...@@ -230,9 +230,11 @@ size_t JobTaskSource::GetRemainingConcurrency() const {
// It is safe to read |state_| without a lock since this variable is atomic, // It is safe to read |state_| without a lock since this variable is atomic,
// and no other state is synchronized with GetRemainingConcurrency(). // and no other state is synchronized with GetRemainingConcurrency().
const auto state = TS_UNCHECKED_READ(state_).Load(); const auto state = TS_UNCHECKED_READ(state_).Load();
if (state.is_canceled())
return 0;
const size_t max_concurrency = GetMaxConcurrency(state.worker_count()); const size_t max_concurrency = GetMaxConcurrency(state.worker_count());
// Avoid underflows. // Avoid underflows.
if (state.is_canceled() || state.worker_count() > max_concurrency) if (state.worker_count() > max_concurrency)
return 0; return 0;
return max_concurrency - state.worker_count(); return max_concurrency - state.worker_count();
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment