Skip to content

Commit 91c502e

Browse files
committed
server: fix handling of RPC timeouts that overflow int64 nanos
1 parent 8d75951 commit 91c502e

File tree

2 files changed

+53
-0
lines changed

2 files changed

+53
-0
lines changed

Diff for: internal/transport/http_util.go

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"encoding/base64"
2525
"fmt"
2626
"io"
27+
"math"
2728
"net"
2829
"net/http"
2930
"strconv"
@@ -435,6 +436,10 @@ func decodeTimeout(s string) (time.Duration, error) {
435436
if size < 2 {
436437
return 0, fmt.Errorf("transport: timeout string is too short: %q", s)
437438
}
439+
if size > 9 {
440+
// Spec allows for 8 digits plus the unit.
441+
return 0, fmt.Errorf("transport: timeout string is too long: %q", s)
442+
}
438443
unit := timeoutUnit(s[size-1])
439444
d, ok := timeoutUnitToDuration(unit)
440445
if !ok {
@@ -444,6 +449,11 @@ func decodeTimeout(s string) (time.Duration, error) {
444449
if err != nil {
445450
return 0, err
446451
}
452+
const maxHours = math.MaxInt64 / int64(time.Hour)
453+
if d == time.Hour && t > maxHours {
454+
// This timeout would overflow math.MaxInt64; clamp it.
455+
return time.Duration(math.MaxInt64), nil
456+
}
447457
return d * time.Duration(t), nil
448458
}
449459

Diff for: test/end2end_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -6778,3 +6778,46 @@ func TestNetPipeConn(t *testing.T) {
67786778
t.Fatalf("UnaryCall(_) = _, %v; want _, nil", err)
67796779
}
67806780
}
6781+
6782+
func TestLargeTimeout(t *testing.T) {
6783+
defer leakcheck.Check(t)
6784+
for _, e := range listTestEnv() {
6785+
testLargeTimeout(t, e)
6786+
}
6787+
}
6788+
6789+
func testLargeTimeout(t *testing.T, e env) {
6790+
te := newTest(t, e)
6791+
te.declareLogNoise("Server.processUnaryRPC failed to write status")
6792+
6793+
ts := &funcServer{}
6794+
te.startServer(ts)
6795+
defer te.tearDown()
6796+
tc := testpb.NewTestServiceClient(te.clientConn())
6797+
6798+
timeouts := []time.Duration{
6799+
time.Duration(math.MaxInt64), // will be (correctly) converted to
6800+
// 2562048 hours, which overflows upon converting back to an int64
6801+
2562047 * time.Hour, // the largest timeout that does not overflow
6802+
}
6803+
6804+
for i, maxTimeout := range timeouts {
6805+
ts.unaryCall = func(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
6806+
deadline, ok := ctx.Deadline()
6807+
timeout := deadline.Sub(time.Now())
6808+
minTimeout := maxTimeout - 5*time.Second
6809+
if !ok || timeout < minTimeout || timeout > maxTimeout {
6810+
t.Errorf("ctx.Deadline() = (now+%v), %v; want [%v, %v], true", timeout, ok, minTimeout, maxTimeout)
6811+
return nil, status.Error(codes.OutOfRange, "deadline error")
6812+
}
6813+
return &testpb.SimpleResponse{}, nil
6814+
}
6815+
6816+
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
6817+
defer cancel()
6818+
6819+
if _, err := tc.UnaryCall(ctx, &testpb.SimpleRequest{}); err != nil {
6820+
t.Errorf("case %v: UnaryCall(_) = _, %v; want _, nil", i, err)
6821+
}
6822+
}
6823+
}

0 commit comments

Comments
 (0)