]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: fix pointer past-the-end in Call with zero-sized return value
authorCherry Zhang <cherryyz@google.com>
Fri, 1 Sep 2017 02:02:37 +0000 (22:02 -0400)
committerCherry Zhang <cherryyz@google.com>
Tue, 19 Sep 2017 20:45:24 +0000 (20:45 +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>
src/reflect/all_test.go
src/reflect/value.go

index 7c83364a4560f812cefb59abf6d536fac81528aa..5d34a6d930c8cd47f9fdfdd344bdbb44da5e6fa1 100644 (file)
@@ -19,6 +19,7 @@ import (
        "strconv"
        "strings"
        "sync"
+       "sync/atomic"
        "testing"
        "time"
        "unicode"
@@ -1629,6 +1630,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 09e007d5603bf68e976cbc09f2e5c3bef0e2c08d..9be5e1f1b8a7d9e1a5de2032688504edde4fac15 100644 (file)
@@ -455,8 +455,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()
                }
        }