Skip to content

Commit 6f07ac2

Browse files
committed
cmd/gc: pad structs which end in zero-sized fields
For a non-zero-sized struct with a final zero-sized field, add a byte to the size (before rounding to alignment). This change ensures that taking the address of the zero-sized field will not incorrectly leak the following object in memory. reflect.funcLayout also needs this treatment. Fixes #9401 Change-Id: I1dc503dc5af4ca22c8f8c048fb7b4541cc957e0f Reviewed-on: https://go-review.googlesource.com/2452 Reviewed-by: Russ Cox <[email protected]>
1 parent 654a185 commit 6f07ac2

File tree

3 files changed

+69
-8
lines changed

3 files changed

+69
-8
lines changed

src/cmd/gc/align.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,13 @@ widstruct(Type *errtype, Type *t, vlong o, int flag)
4848
Type *f;
4949
int64 w;
5050
int32 maxalign;
51+
vlong starto, lastzero;
5152

53+
starto = o;
5254
maxalign = flag;
5355
if(maxalign < 1)
5456
maxalign = 1;
57+
lastzero = 0;
5558
for(f=t->type; f!=T; f=f->down) {
5659
if(f->etype != TFIELD)
5760
fatal("widstruct: not TFIELD: %lT", f);
@@ -80,22 +83,28 @@ widstruct(Type *errtype, Type *t, vlong o, int flag)
8083
} else
8184
f->nname->xoffset = o;
8285
}
86+
if(w == 0)
87+
lastzero = o;
8388
o += w;
8489
if(o >= MAXWIDTH) {
8590
yyerror("type %lT too large", errtype);
8691
o = 8; // small but nonzero
8792
}
8893
}
94+
// For nonzero-sized structs which end in a zero-sized thing, we add
95+
// an extra byte of padding to the type. This padding ensures that
96+
// taking the address of the zero-sized thing can't manufacture a
97+
// pointer to the next object in the heap. See issue 9401.
98+
if(flag == 1 && o > starto && o == lastzero)
99+
o++;
100+
89101
// final width is rounded
90102
if(flag)
91103
o = rnd(o, maxalign);
92104
t->align = maxalign;
93105

94106
// type width only includes back to first field's offset
95-
if(t->type == T)
96-
t->width = 0;
97-
else
98-
t->width = o - t->type->width;
107+
t->width = o - starto;
99108
return o;
100109
}
101110

src/reflect/type.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,9 +1527,10 @@ func isReflexive(t *rtype) bool {
15271527

15281528
// gcProg is a helper type for generatation of GC pointer info.
15291529
type gcProg struct {
1530-
gc []byte
1531-
size uintptr // size of type in bytes
1532-
hasPtr bool
1530+
gc []byte
1531+
size uintptr // size of type in bytes
1532+
hasPtr bool
1533+
lastZero uintptr // largest offset of a zero-byte field
15331534
}
15341535

15351536
func (gc *gcProg) append(v byte) {
@@ -1542,6 +1543,9 @@ func (gc *gcProg) appendProg(t *rtype) {
15421543
gc.align(uintptr(t.align))
15431544
if !t.pointers() {
15441545
gc.size += t.size
1546+
if t.size == 0 {
1547+
gc.lastZero = gc.size
1548+
}
15451549
return
15461550
}
15471551
switch t.Kind() {
@@ -1566,11 +1570,15 @@ func (gc *gcProg) appendProg(t *rtype) {
15661570
gc.appendWord(bitsPointer)
15671571
gc.appendWord(bitsPointer)
15681572
case Struct:
1573+
oldsize := gc.size
15691574
c := t.NumField()
15701575
for i := 0; i < c; i++ {
15711576
gc.appendProg(t.Field(i).Type.common())
15721577
}
1573-
gc.align(uintptr(t.align))
1578+
if gc.size > oldsize + t.size {
1579+
panic("reflect: struct components are larger than the struct itself")
1580+
}
1581+
gc.size = oldsize + t.size
15741582
}
15751583
}
15761584

@@ -1595,6 +1603,9 @@ func (gc *gcProg) finalize() (unsafe.Pointer, bool) {
15951603
if gc.size == 0 {
15961604
return nil, false
15971605
}
1606+
if gc.lastZero == gc.size {
1607+
gc.size++
1608+
}
15981609
ptrsize := unsafe.Sizeof(uintptr(0))
15991610
gc.align(ptrsize)
16001611
nptr := gc.size / ptrsize

src/runtime/runtime_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,44 @@ func TestEqString(t *testing.T) {
247247
}
248248
}
249249
}
250+
251+
func TestTrailingZero(t *testing.T) {
252+
// make sure we add padding for structs with trailing zero-sized fields
253+
type T1 struct {
254+
n int32
255+
z [0]byte
256+
}
257+
if unsafe.Sizeof(T1{}) != 8 {
258+
t.Errorf("sizeof(%#v)==%d, want 8", T1{}, unsafe.Sizeof(T1{}))
259+
}
260+
type T2 struct {
261+
n int64
262+
z struct{}
263+
}
264+
if unsafe.Sizeof(T2{}) != 16 {
265+
t.Errorf("sizeof(%#v)==%d, want 16", T2{}, unsafe.Sizeof(T2{}))
266+
}
267+
type T3 struct {
268+
n byte
269+
z [4]struct{}
270+
}
271+
if unsafe.Sizeof(T3{}) != 2 {
272+
t.Errorf("sizeof(%#v)==%d, want 2", T3{}, unsafe.Sizeof(T3{}))
273+
}
274+
// make sure padding can double for both zerosize and alignment
275+
type T4 struct {
276+
a int32
277+
b int16
278+
c int8
279+
z struct{}
280+
}
281+
if unsafe.Sizeof(T4{}) != 8 {
282+
t.Errorf("sizeof(%#v)==%d, want 8", T4{}, unsafe.Sizeof(T4{}))
283+
}
284+
// make sure we don't pad a zero-sized thing
285+
type T5 struct {
286+
}
287+
if unsafe.Sizeof(T5{}) != 0 {
288+
t.Errorf("sizeof(%#v)==%d, want 0", T5{}, unsafe.Sizeof(T5{}))
289+
}
290+
}

0 commit comments

Comments
 (0)