]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.9] reflect: fix pointer past-the-end in Call with zero-sized...
authorCherry Zhang <cherryyz@google.com>
Fri, 1 Sep 2017 02:02:37 +0000 (22:02 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 25 Oct 2017 20:23:00 +0000 (20:23 +0000)
If a function with nonzero frame but zero-sized return value is
Call'd, we may write a past-the-end pointer in preparing the
return Values. Fix by return the zero value for zero-sized
return value.

Fixes #21717.

Change-Id: I5351cd86d898467170a888b4c3fc9392f0e7aa3b
Reviewed-on: https://go-review.googlesource.com/60811
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
Reviewed-on: https://go-review.googlesource.com/70971
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
src/reflect/all_test.go
src/reflect/value.go

index 5a5c91b7512037d06edeb1fdaa98297bc9616e42..33694fd10ea0bed8c1c44d91d365c9df0059db84 100644 (file)
@@ -19,6 +19,7 @@ import (
        "strconv"
        "strings"
        "sync"
+       "sync/atomic"
        "testing"
        "time"
        "unicode"
@@ -1546,6 +1547,30 @@ func TestCallWithStruct(t *testing.T) {
        }
 }
 
+func TestCallReturnsEmpty(t *testing.T) {
+       // Issue 21717: past-the-end pointer write in Call with
+       // nonzero-sized frame and zero-sized return value.
+       runtime.GC()
+       var finalized uint32
+       f := func() (emptyStruct, *int) {
+               i := new(int)
+               runtime.SetFinalizer(i, func(*int) { atomic.StoreUint32(&finalized, 1) })
+               return emptyStruct{}, i
+       }
+       v := ValueOf(f).Call(nil)[0] // out[0] should not alias out[1]'s memory, so the finalizer should run.
+       timeout := time.After(5 * time.Second)
+       for atomic.LoadUint32(&finalized) == 0 {
+               select {
+               case <-timeout:
+                       t.Fatal("finalizer did not run")
+               default:
+               }
+               runtime.Gosched()
+               runtime.GC()
+       }
+       runtime.KeepAlive(v)
+}
+
 func BenchmarkCall(b *testing.B) {
        fv := ValueOf(func(a, b string) {})
        b.ReportAllocs()
index 8488e8dec120dedf8ad3c15ef014f7917f10d0ac..2b0ca05c702eb2acdebb7584870f172c54a44ec3 100644 (file)
@@ -456,8 +456,14 @@ func (v Value) call(op string, in []Value) []Value {
                        tv := t.Out(i)
                        a := uintptr(tv.Align())
                        off = (off + a - 1) &^ (a - 1)
-                       fl := flagIndir | flag(tv.Kind())
-                       ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(args) + off), fl}
+                       if tv.Size() != 0 {
+                               fl := flagIndir | flag(tv.Kind())
+                               ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(args) + off), fl}
+                       } else {
+                               // For zero-sized return value, args+off may point to the next object.
+                               // In this case, return the zero value instead.
+                               ret[i] = Zero(tv)
+                       }
                        off += tv.Size()
                }
        }