[RFC,v3] timekeeping: Limit the sleep time to avoid overflow

From: Chen Yu
Date: Sat Jul 30 2016 - 11:21:02 EST

It is reported the hibernation fails at 2nd attempt, which
hangs at hibernate() -> syscore_resume() -> i8237A_resume()
-> claim_dma_lock(), because the lock has already been taken.
However there is actually no other process would like to grab
this lock on that problematic platform.

Further investigation shows that, the problem is triggered by
setting /sys/power/pm_trace to 1 before the 1st hibernation.
Since once pm_trace is enabled, the rtc becomes unmeaningful
after suspend, and meanwhile some BIOSes would like to adjust
the 'invalid' tsc(e.g, smaller than 1970) to the release date
of that motherboard during POST stage, thus after resumed, a
significant long sleep time might be generated due to meaningless
tsc delta, thus in timekeeping_resume -> tk_debug_account_sleep_time,
if the bit31 happened to be set to 1, the fls returns 32 and then we
add 1 to sleep_time_bin[32], which caused a memory overwritten.
As depicted by System.map:

ffffffff81c9d080 b sleep_time_bin
ffffffff81c9d100 B dma_spin_lock

the dma_spin_lock.val is set to 1, which caused this problem.

In theory we can avoid the overflow by ignoring the idle injection
if pm_trace is enabled, but we might still miss other cases which
might also break the rtc, e.g, buggy clocksoure/rtc driver,
or even user space tool such as hwclock -- so there is no generic
method to dertermin whether we should trust the tsc.

A simpler way is to set the threshold for the sleep time, and
ignore those abnormal ones. This patch sets the upper limit of
sleep seconds to 0x7fffffff, since no one is likely to sleep
that long(68 years).

Cc: Stable <stable@xxxxxxxxxxxxxxx> # 3.17+
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: Rafael J. Wysocki <rjw@xxxxxxxxxxxxx>
Cc: John Stultz <john.stultz@xxxxxxxxxx>
Cc: Xunlei Pang <xpang@xxxxxxxxxx>
Cc: Zhang Rui <rui.zhang@xxxxxxxxx>
Cc: linux-kernel@xxxxxxxxxxxxxxx
Cc: linux-pm@xxxxxxxxxxxxxxx
Reported-and-tested-by: Janek Kozicki <cosurgi@xxxxxxxxx>
Signed-off-by: Chen Yu <yu.c.chen@xxxxxxxxx>
kernel/time/timekeeping.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 3b65746..17bc72c 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -1509,6 +1509,7 @@ void __init timekeeping_init(void)

/* time in seconds when suspend began for persistent clock */
static struct timespec64 timekeeping_suspend_time;
+#define MAX_SLEEP_TIME 0x7fffffff

* __timekeeping_inject_sleeptime - Internal function to add sleep interval
@@ -1520,7 +1521,8 @@ static struct timespec64 timekeeping_suspend_time;
static void __timekeeping_inject_sleeptime(struct timekeeper *tk,
struct timespec64 *delta)
- if (!timespec64_valid_strict(delta)) {
+ if (!timespec64_valid_strict(delta) ||
+ delta->tv_sec > MAX_SLEEP_TIME) {
"__timekeeping_inject_sleeptime: Invalid "
"sleep delta value!\n");