Skip to content

Commit b78b427

Browse files
committed
runtime, time: strictly enforce when, period constraints
timer.when must always be positive. addtimer and modtimer already check that it is non-negative; we expand it to include zero. Also upgrade from pinning bad values to throwing, as these values shouldn't be possible to pass (except as below). timeSleep may overflow timer.nextwhen. This would previously have been pinned by resetForSleep, now we fix it manually. runOneTimer may overflow timer.when when adding timer.period. Detect this and pin to maxWhen. addtimer is now too strict to allow TestOverflowRuntimeTimer to test an overflowed timer. Such a timer should not be possible; to help guard against accidental inclusion siftup / siftdown will check timers as it goes. This has been replaced with tests for period and sleep overflows. Change-Id: I17f9739e27ebcb20d87945c635050316fb8e9226 Reviewed-on: https://go-review.googlesource.com/c/go/+/274853 Trust: Michael Pratt <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent b635e4b commit b78b427

File tree

4 files changed

+60
-38
lines changed

4 files changed

+60
-38
lines changed

src/runtime/time.go

+25-6
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ func timeSleep(ns int64) {
187187
t.f = goroutineReady
188188
t.arg = gp
189189
t.nextwhen = nanotime() + ns
190+
if t.nextwhen < 0 { // check for overflow.
191+
t.nextwhen = maxWhen
192+
}
190193
gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
191194
}
192195

@@ -244,10 +247,14 @@ func goroutineReady(arg interface{}, seq uintptr) {
244247
// That avoids the risk of changing the when field of a timer in some P's heap,
245248
// which could cause the heap to become unsorted.
246249
func addtimer(t *timer) {
247-
// when must never be negative; otherwise runtimer will overflow
248-
// during its delta calculation and never expire other runtime timers.
249-
if t.when < 0 {
250-
t.when = maxWhen
250+
// when must be positive. A negative value will cause runtimer to
251+
// overflow during its delta calculation and never expire other runtime
252+
// timers. Zero will cause checkTimers to fail to notice the timer.
253+
if t.when <= 0 {
254+
throw("timer when must be positive")
255+
}
256+
if t.period < 0 {
257+
throw("timer period must be non-negative")
251258
}
252259
if t.status != timerNoStatus {
253260
throw("addtimer called with initialized timer")
@@ -408,8 +415,11 @@ func dodeltimer0(pp *p) {
408415
// This is called by the netpoll code or time.Ticker.Reset or time.Timer.Reset.
409416
// Reports whether the timer was modified before it was run.
410417
func modtimer(t *timer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr) bool {
411-
if when < 0 {
412-
when = maxWhen
418+
if when <= 0 {
419+
throw("timer when must be positive")
420+
}
421+
if period < 0 {
422+
throw("timer period must be non-negative")
413423
}
414424

415425
status := uint32(timerNoStatus)
@@ -848,6 +858,9 @@ func runOneTimer(pp *p, t *timer, now int64) {
848858
// Leave in heap but adjust next time to fire.
849859
delta := t.when - now
850860
t.when += t.period * (1 + -delta/t.period)
861+
if t.when < 0 { // check for overflow.
862+
t.when = maxWhen
863+
}
851864
siftdownTimer(pp.timers, 0)
852865
if !atomic.Cas(&t.status, timerRunning, timerWaiting) {
853866
badTimer()
@@ -1066,6 +1079,9 @@ func siftupTimer(t []*timer, i int) {
10661079
badTimer()
10671080
}
10681081
when := t[i].when
1082+
if when <= 0 {
1083+
badTimer()
1084+
}
10691085
tmp := t[i]
10701086
for i > 0 {
10711087
p := (i - 1) / 4 // parent
@@ -1086,6 +1102,9 @@ func siftdownTimer(t []*timer, i int) {
10861102
badTimer()
10871103
}
10881104
when := t[i].when
1105+
if when <= 0 {
1106+
badTimer()
1107+
}
10891108
tmp := t[i]
10901109
for {
10911110
c := i*4 + 1 // left child

src/time/internal_test.go

+17-25
Original file line numberDiff line numberDiff line change
@@ -33,38 +33,30 @@ var DaysIn = daysIn
3333

3434
func empty(arg interface{}, seq uintptr) {}
3535

36-
// Test that a runtimeTimer with a duration so large it overflows
37-
// does not cause other timers to hang.
36+
// Test that a runtimeTimer with a period that would overflow when on
37+
// expiration does not throw or cause other timers to hang.
3838
//
3939
// This test has to be in internal_test.go since it fiddles with
4040
// unexported data structures.
41-
func CheckRuntimeTimerOverflow() {
42-
// We manually create a runtimeTimer to bypass the overflow
43-
// detection logic in NewTimer: we're testing the underlying
44-
// runtime.addtimer function.
41+
func CheckRuntimeTimerPeriodOverflow() {
42+
// We manually create a runtimeTimer with huge period, but that expires
43+
// immediately. The public Timer interface would require waiting for
44+
// the entire period before the first update.
4545
r := &runtimeTimer{
46-
when: runtimeNano() + (1<<63 - 1),
47-
f: empty,
48-
arg: nil,
46+
when: runtimeNano(),
47+
period: 1<<63 - 1,
48+
f: empty,
49+
arg: nil,
4950
}
5051
startTimer(r)
52+
defer stopTimer(r)
5153

52-
// Start a goroutine that should send on t.C right away.
53-
t := NewTimer(1)
54-
55-
defer func() {
56-
stopTimer(r)
57-
t.Stop()
58-
}()
59-
60-
// If the test fails, we will hang here until the timeout in the
61-
// testing package fires, which is 10 minutes. It would be nice to
62-
// catch the problem sooner, but there is no reliable way to guarantee
63-
// that timers are run without doing something involving the scheduler.
64-
// Previous failed attempts have tried calling runtime.Gosched and
65-
// runtime.GC, but neither is reliable. So we fall back to hope:
66-
// We hope we don't hang here.
67-
<-t.C
54+
// If this test fails, we will either throw (when siftdownTimer detects
55+
// bad when on update), or other timers will hang (if the timer in a
56+
// heap is in a bad state). There is no reliable way to test this, but
57+
// we wait on a short timer here as a smoke test (alternatively, timers
58+
// in later tests may hang).
59+
<-After(25 * Millisecond)
6860
}
6961

7062
var (

src/time/sleep.go

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func when(d Duration) int64 {
3131
}
3232
t := runtimeNano() + int64(d)
3333
if t < 0 {
34+
// N.B. runtimeNano() and d are always positive, so addition
35+
// (including overflow) will never result in t == 0.
3436
t = 1<<63 - 1 // math.MaxInt64
3537
}
3638
return t

src/time/sleep_test.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -434,17 +434,29 @@ func TestReset(t *testing.T) {
434434
t.Error(err)
435435
}
436436

437-
// Test that sleeping for an interval so large it overflows does not
438-
// result in a short sleep duration.
437+
// Test that sleeping (via Sleep or Timer) for an interval so large it
438+
// overflows does not result in a short sleep duration. Nor does it interfere
439+
// with execution of other timers. If it does, timers in this or subsequent
440+
// tests may not fire.
439441
func TestOverflowSleep(t *testing.T) {
440442
const big = Duration(int64(1<<63 - 1))
443+
444+
go func() {
445+
Sleep(big)
446+
// On failure, this may return after the test has completed, so
447+
// we need to panic instead.
448+
panic("big sleep returned")
449+
}()
450+
441451
select {
442452
case <-After(big):
443453
t.Fatalf("big timeout fired")
444454
case <-After(25 * Millisecond):
445455
// OK
446456
}
457+
447458
const neg = Duration(-1 << 63)
459+
Sleep(neg) // Returns immediately.
448460
select {
449461
case <-After(neg):
450462
// OK
@@ -473,13 +485,10 @@ func TestIssue5745(t *testing.T) {
473485
t.Error("Should be unreachable.")
474486
}
475487

476-
func TestOverflowRuntimeTimer(t *testing.T) {
477-
if testing.Short() {
478-
t.Skip("skipping in short mode, see issue 6874")
479-
}
488+
func TestOverflowPeriodRuntimeTimer(t *testing.T) {
480489
// This may hang forever if timers are broken. See comment near
481490
// the end of CheckRuntimeTimerOverflow in internal_test.go.
482-
CheckRuntimeTimerOverflow()
491+
CheckRuntimeTimerPeriodOverflow()
483492
}
484493

485494
func checkZeroPanicString(t *testing.T) {

0 commit comments

Comments
 (0)