diff options
author | Evan Green <evgreen@chromium.org> | 2021-04-06 09:41:14 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-04-09 02:03:51 +0000 |
commit | 9ad2f2584157e450e44a6231357b2b7f1eefdf48 (patch) | |
tree | 1a9868f4e5e8a92545fc5c0f3e596d35bf5155b7 | |
parent | 458dceca8ca8872b53be893ceab77fa27958707b (diff) | |
download | chrome-ec-9ad2f2584157e450e44a6231357b2b7f1eefdf48.tar.gz |
hooks: Avoid torn accesses in the hook task
The hook task runs at the lowest priority, and both reads from and
writes to a data structure that can be changed out from under it at any
time. This is unsafe, and can cause missed hook events and double hook
events.
For example, the hook task reads __deferred_until[i], a 64-bit value, in
two 32-bit reads. If the hook task is interrupted during this read, and
the interruption changes the value, the hook task may read a totally
bogus value. This is rare, as overflows across this 32-bit boundary
don't happen often, but leads to unpredicable behavior when they do.
The writes the hook task does are also problematic, since for instance
the hook may have been rescheduled just after the slow old hook task
entered its if clause deciding to run the hook, but before it clobbered
__deferred_until[i] back to zero. Things get worse if the hook routine
pointer ever changes, though I don't think we're currently doing that
anywhere today.
Instead, disable interrupts while the hook data structure is being
manipulated as a makeshift lock around it. Remove the defer_new_call
variable as well, since the new deadline is now computed atomically (and
without the possibility of torn reads).
BUG=b:178660461, b:179062230
BRANCH=None
TEST=Run suspend 2500 on Boten, observe no spurious s0ix timeouts.
Signed-off-by: Evan Green <evgreen@chromium.org>
Change-Id: Iff0d596014e1e79dd9691d363fdc8e54bfe2dff0
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2805222
Reviewed-by: Wai-Hong Tam <waihong@google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2816156
Tested-by: Abe Levkoy <alevkoy@chromium.org>
Reviewed-by: Abe Levkoy <alevkoy@chromium.org>
Commit-Queue: Abe Levkoy <alevkoy@chromium.org>
-rw-r--r-- | common/hooks.c | 28 |
1 files changed, 12 insertions, 16 deletions
diff --git a/common/hooks.c b/common/hooks.c index 4c2a4e48c6..a491d026a4 100644 --- a/common/hooks.c +++ b/common/hooks.c @@ -65,7 +65,6 @@ static const struct hook_ptrs hook_list[] = { }; /* Times for deferrable functions */ -static int defer_new_call; static int hook_task_started; #ifdef CONFIG_HOOK_DEBUG @@ -157,12 +156,6 @@ int hook_call_deferred(const struct deferred_data *data, int us) } else { /* Set alarm */ __deferred_until[i] = get_time().val + us; - /* - * Flag that hook_call_deferred() has been called. If the hook - * task is already active, this will allow it to go through the - * loop one more time before sleeping. - */ - defer_new_call = 1; /* Wake task so it can re-sleep for the proper time */ if (hook_task_started) @@ -191,20 +184,24 @@ void hook_task(void *u) int next = 0; int i; + interrupt_disable(); /* Handle deferred routines */ for (i = 0; i < DEFERRED_FUNCS_COUNT; i++) { if (__deferred_until[i] && __deferred_until[i] < t) { - CPRINTS("hook call deferred 0x%pP", - __deferred_funcs[i].routine); /* * Call deferred function. Clear timer first, * so it can request itself be called later. */ __deferred_until[i] = 0; + interrupt_enable(); + CPRINTS("hook call deferred 0x%pP", + __deferred_funcs[i].routine); __deferred_funcs[i].routine(); + interrupt_disable(); } } + interrupt_enable(); if (t - last_tick >= HOOK_TICK_INTERVAL) { #ifdef CONFIG_HOOK_DEBUG record_hook_delay(t, last_tick, HOOK_TICK_INTERVAL, @@ -230,9 +227,7 @@ void hook_task(void *u) if (last_tick + HOOK_TICK_INTERVAL > t) next = last_tick + HOOK_TICK_INTERVAL - t; - /* Wake earlier if needed by a deferred routine */ - defer_new_call = 0; - + interrupt_disable(); for (i = 0; i < DEFERRED_FUNCS_COUNT && next > 0; i++) { if (!__deferred_until[i]) continue; @@ -243,12 +238,13 @@ void hook_task(void *u) next = __deferred_until[i] - t; } + interrupt_enable(); + /* - * If nothing is immediately pending, and hook_call_deferred() - * hasn't been called since we started calculating next, sleep - * until the next event. + * If nothing is immediately pending, sleep until the next + * event. */ - if (next > 0 && !defer_new_call) + if (next > 0) task_wait_event(next); } } |