1
0
mirror of https://github.com/golang/go synced 2024-11-26 09:58:04 -07:00

cmd/compile, runtime: use __msan_memmove for moving data, split msanread to fields

Currently, for data moving, we generate an msanread of the source,
followed by an msanwrite of the destination. msanread checks
the source is initialized.

This has a problem: if the source is an aggregate type containing
alignment paddings, the padding bytes may not be thought as
initialized by MSAN. If we copy the aggregate type by value, if
it counts as a read, MSAN reports using uninitialized data. This
CL changes it to use __msan_memmove for data copying, which tells
MSAN to propagate initialized-ness but not check for it.

Caveat: technically __msan_memmove is not a public API of MSAN,
although the C compiler does generate direct calls to it.

Also, when instrumenting a load of a struct, split the
instrumentation to fields, instead of generating an msanread for
the whole struct. This skips padding bytes, which may not be
considered initialized in MSAN.

Fixes #42820.

Change-Id: Id861c8bbfd94cfcccefcc58eaf9e4eb43b4d85c6
Reviewed-on: https://go-review.googlesource.com/c/go/+/270859
Trust: Cherry Zhang <cherryyz@google.com>
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
This commit is contained in:
Cherry Zhang 2020-11-16 21:28:26 -05:00
parent d0c0dc682c
commit 07cba70d57
9 changed files with 158 additions and 33 deletions

View File

@ -28,6 +28,7 @@ func TestMSAN(t *testing.T) {
{src: "msan4.go"}, {src: "msan4.go"},
{src: "msan5.go"}, {src: "msan5.go"},
{src: "msan6.go"}, {src: "msan6.go"},
{src: "msan7.go"},
{src: "msan_fail.go", wantErr: true}, {src: "msan_fail.go", wantErr: true},
} }
for _, tc := range cases { for _, tc := range cases {

View File

@ -0,0 +1,38 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
// Test passing C struct to exported Go function.
/*
#include <stdint.h>
#include <stdlib.h>
// T is a C struct with alignment padding after b.
// The padding bytes are not considered initialized by MSAN.
// It is big enough to be passed on stack in C ABI (and least
// on AMD64).
typedef struct { char b; uintptr_t x, y; } T;
extern void F(T);
// Use weak as a hack to permit defining a function even though we use export.
void CF(int x) __attribute__ ((weak));
void CF(int x) {
T *t = malloc(sizeof(T));
t->b = (char)x;
t->x = x;
t->y = x;
F(*t);
}
*/
import "C"
//export F
func F(t C.T) { println(t.b, t.x, t.y) }
func main() {
C.CF(C.int(0))
}

View File

@ -184,16 +184,17 @@ var runtimeDecls = [...]struct {
{"racewriterange", funcTag, 121}, {"racewriterange", funcTag, 121},
{"msanread", funcTag, 121}, {"msanread", funcTag, 121},
{"msanwrite", funcTag, 121}, {"msanwrite", funcTag, 121},
{"checkptrAlignment", funcTag, 122}, {"msanmove", funcTag, 122},
{"checkptrArithmetic", funcTag, 124}, {"checkptrAlignment", funcTag, 123},
{"libfuzzerTraceCmp1", funcTag, 126}, {"checkptrArithmetic", funcTag, 125},
{"libfuzzerTraceCmp2", funcTag, 128}, {"libfuzzerTraceCmp1", funcTag, 127},
{"libfuzzerTraceCmp4", funcTag, 129}, {"libfuzzerTraceCmp2", funcTag, 129},
{"libfuzzerTraceCmp8", funcTag, 130}, {"libfuzzerTraceCmp4", funcTag, 130},
{"libfuzzerTraceConstCmp1", funcTag, 126}, {"libfuzzerTraceCmp8", funcTag, 131},
{"libfuzzerTraceConstCmp2", funcTag, 128}, {"libfuzzerTraceConstCmp1", funcTag, 127},
{"libfuzzerTraceConstCmp4", funcTag, 129}, {"libfuzzerTraceConstCmp2", funcTag, 129},
{"libfuzzerTraceConstCmp8", funcTag, 130}, {"libfuzzerTraceConstCmp4", funcTag, 130},
{"libfuzzerTraceConstCmp8", funcTag, 131},
{"x86HasPOPCNT", varTag, 6}, {"x86HasPOPCNT", varTag, 6},
{"x86HasSSE41", varTag, 6}, {"x86HasSSE41", varTag, 6},
{"x86HasFMA", varTag, 6}, {"x86HasFMA", varTag, 6},
@ -202,7 +203,7 @@ var runtimeDecls = [...]struct {
} }
func runtimeTypes() []*types.Type { func runtimeTypes() []*types.Type {
var typs [131]*types.Type var typs [132]*types.Type
typs[0] = types.Bytetype typs[0] = types.Bytetype
typs[1] = types.NewPtr(typs[0]) typs[1] = types.NewPtr(typs[0])
typs[2] = types.Types[TANY] typs[2] = types.Types[TANY]
@ -325,14 +326,15 @@ func runtimeTypes() []*types.Type {
typs[119] = functype(nil, []*Node{anonfield(typs[65])}, []*Node{anonfield(typs[20])}) typs[119] = functype(nil, []*Node{anonfield(typs[65])}, []*Node{anonfield(typs[20])})
typs[120] = functype(nil, []*Node{anonfield(typs[26]), anonfield(typs[26])}, []*Node{anonfield(typs[26])}) typs[120] = functype(nil, []*Node{anonfield(typs[26]), anonfield(typs[26])}, []*Node{anonfield(typs[26])})
typs[121] = functype(nil, []*Node{anonfield(typs[5]), anonfield(typs[5])}, nil) typs[121] = functype(nil, []*Node{anonfield(typs[5]), anonfield(typs[5])}, nil)
typs[122] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[1]), anonfield(typs[5])}, nil) typs[122] = functype(nil, []*Node{anonfield(typs[5]), anonfield(typs[5]), anonfield(typs[5])}, nil)
typs[123] = types.NewSlice(typs[7]) typs[123] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[1]), anonfield(typs[5])}, nil)
typs[124] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[123])}, nil) typs[124] = types.NewSlice(typs[7])
typs[125] = types.Types[TUINT8] typs[125] = functype(nil, []*Node{anonfield(typs[7]), anonfield(typs[124])}, nil)
typs[126] = functype(nil, []*Node{anonfield(typs[125]), anonfield(typs[125])}, nil) typs[126] = types.Types[TUINT8]
typs[127] = types.Types[TUINT16] typs[127] = functype(nil, []*Node{anonfield(typs[126]), anonfield(typs[126])}, nil)
typs[128] = functype(nil, []*Node{anonfield(typs[127]), anonfield(typs[127])}, nil) typs[128] = types.Types[TUINT16]
typs[129] = functype(nil, []*Node{anonfield(typs[65]), anonfield(typs[65])}, nil) typs[129] = functype(nil, []*Node{anonfield(typs[128]), anonfield(typs[128])}, nil)
typs[130] = functype(nil, []*Node{anonfield(typs[24]), anonfield(typs[24])}, nil) typs[130] = functype(nil, []*Node{anonfield(typs[65]), anonfield(typs[65])}, nil)
typs[131] = functype(nil, []*Node{anonfield(typs[24]), anonfield(typs[24])}, nil)
return typs[:] return typs[:]
} }

View File

@ -237,6 +237,7 @@ func racewriterange(addr, size uintptr)
// memory sanitizer // memory sanitizer
func msanread(addr, size uintptr) func msanread(addr, size uintptr)
func msanwrite(addr, size uintptr) func msanwrite(addr, size uintptr)
func msanmove(dst, src, size uintptr)
func checkptrAlignment(unsafe.Pointer, *byte, uintptr) func checkptrAlignment(unsafe.Pointer, *byte, uintptr)
func checkptrArithmetic(unsafe.Pointer, []unsafe.Pointer) func checkptrArithmetic(unsafe.Pointer, []unsafe.Pointer)

View File

@ -309,6 +309,7 @@ var (
growslice, growslice,
msanread, msanread,
msanwrite, msanwrite,
msanmove,
newobject, newobject,
newproc, newproc,
panicdivide, panicdivide,

View File

@ -79,6 +79,7 @@ func initssaconfig() {
growslice = sysfunc("growslice") growslice = sysfunc("growslice")
msanread = sysfunc("msanread") msanread = sysfunc("msanread")
msanwrite = sysfunc("msanwrite") msanwrite = sysfunc("msanwrite")
msanmove = sysfunc("msanmove")
newobject = sysfunc("newobject") newobject = sysfunc("newobject")
newproc = sysfunc("newproc") newproc = sysfunc("newproc")
panicdivide = sysfunc("panicdivide") panicdivide = sysfunc("panicdivide")
@ -966,7 +967,45 @@ func (s *state) newValueOrSfCall2(op ssa.Op, t *types.Type, arg0, arg1 *ssa.Valu
return s.newValue2(op, t, arg0, arg1) return s.newValue2(op, t, arg0, arg1)
} }
func (s *state) instrument(t *types.Type, addr *ssa.Value, wr bool) { type instrumentKind uint8
const (
instrumentRead = iota
instrumentWrite
instrumentMove
)
func (s *state) instrument(t *types.Type, addr *ssa.Value, kind instrumentKind) {
s.instrument2(t, addr, nil, kind)
}
// instrumentFields instruments a read/write operation on addr.
// If it is instrumenting for MSAN and t is a struct type, it instruments
// operation for each field, instead of for the whole struct.
func (s *state) instrumentFields(t *types.Type, addr *ssa.Value, kind instrumentKind) {
if !flag_msan || !t.IsStruct() {
s.instrument(t, addr, kind)
return
}
for _, f := range t.Fields().Slice() {
if f.Sym.IsBlank() {
continue
}
offptr := s.newValue1I(ssa.OpOffPtr, types.NewPtr(f.Type), f.Offset, addr)
s.instrumentFields(f.Type, offptr, kind)
}
}
func (s *state) instrumentMove(t *types.Type, dst, src *ssa.Value) {
if flag_msan {
s.instrument2(t, dst, src, instrumentMove)
} else {
s.instrument(t, src, instrumentRead)
s.instrument(t, dst, instrumentWrite)
}
}
func (s *state) instrument2(t *types.Type, addr, addr2 *ssa.Value, kind instrumentKind) {
if !s.curfn.Func.InstrumentBody() { if !s.curfn.Func.InstrumentBody() {
return return
} }
@ -983,33 +1022,54 @@ func (s *state) instrument(t *types.Type, addr *ssa.Value, wr bool) {
var fn *obj.LSym var fn *obj.LSym
needWidth := false needWidth := false
if addr2 != nil && kind != instrumentMove {
panic("instrument2: non-nil addr2 for non-move instrumentation")
}
if flag_msan { if flag_msan {
fn = msanread switch kind {
if wr { case instrumentRead:
fn = msanread
case instrumentWrite:
fn = msanwrite fn = msanwrite
case instrumentMove:
fn = msanmove
default:
panic("unreachable")
} }
needWidth = true needWidth = true
} else if flag_race && t.NumComponents(types.CountBlankFields) > 1 { } else if flag_race && t.NumComponents(types.CountBlankFields) > 1 {
// for composite objects we have to write every address // for composite objects we have to write every address
// because a write might happen to any subobject. // because a write might happen to any subobject.
// composites with only one element don't have subobjects, though. // composites with only one element don't have subobjects, though.
fn = racereadrange switch kind {
if wr { case instrumentRead:
fn = racereadrange
case instrumentWrite:
fn = racewriterange fn = racewriterange
default:
panic("unreachable")
} }
needWidth = true needWidth = true
} else if flag_race { } else if flag_race {
// for non-composite objects we can write just the start // for non-composite objects we can write just the start
// address, as any write must write the first byte. // address, as any write must write the first byte.
fn = raceread switch kind {
if wr { case instrumentRead:
fn = raceread
case instrumentWrite:
fn = racewrite fn = racewrite
default:
panic("unreachable")
} }
} else { } else {
panic("unreachable") panic("unreachable")
} }
args := []*ssa.Value{addr} args := []*ssa.Value{addr}
if addr2 != nil {
args = append(args, addr2)
}
if needWidth { if needWidth {
args = append(args, s.constInt(types.Types[TUINTPTR], w)) args = append(args, s.constInt(types.Types[TUINTPTR], w))
} }
@ -1017,7 +1077,7 @@ func (s *state) instrument(t *types.Type, addr *ssa.Value, wr bool) {
} }
func (s *state) load(t *types.Type, src *ssa.Value) *ssa.Value { func (s *state) load(t *types.Type, src *ssa.Value) *ssa.Value {
s.instrument(t, src, false) s.instrumentFields(t, src, instrumentRead)
return s.rawLoad(t, src) return s.rawLoad(t, src)
} }
@ -1030,15 +1090,14 @@ func (s *state) store(t *types.Type, dst, val *ssa.Value) {
} }
func (s *state) zero(t *types.Type, dst *ssa.Value) { func (s *state) zero(t *types.Type, dst *ssa.Value) {
s.instrument(t, dst, true) s.instrument(t, dst, instrumentWrite)
store := s.newValue2I(ssa.OpZero, types.TypeMem, t.Size(), dst, s.mem()) store := s.newValue2I(ssa.OpZero, types.TypeMem, t.Size(), dst, s.mem())
store.Aux = t store.Aux = t
s.vars[&memVar] = store s.vars[&memVar] = store
} }
func (s *state) move(t *types.Type, dst, src *ssa.Value) { func (s *state) move(t *types.Type, dst, src *ssa.Value) {
s.instrument(t, src, false) s.instrumentMove(t, dst, src)
s.instrument(t, dst, true)
store := s.newValue3I(ssa.OpMove, types.TypeMem, t.Size(), dst, src, s.mem()) store := s.newValue3I(ssa.OpMove, types.TypeMem, t.Size(), dst, src, s.mem())
store.Aux = t store.Aux = t
s.vars[&memVar] = store s.vars[&memVar] = store
@ -5248,7 +5307,7 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args .
// do *left = right for type t. // do *left = right for type t.
func (s *state) storeType(t *types.Type, left, right *ssa.Value, skip skipMask, leftIsStmt bool) { func (s *state) storeType(t *types.Type, left, right *ssa.Value, skip skipMask, leftIsStmt bool) {
s.instrument(t, left, true) s.instrument(t, left, instrumentWrite)
if skip == 0 && (!t.HasPointers() || ssa.IsStackAddr(left)) { if skip == 0 && (!t.HasPointers() || ssa.IsStackAddr(left)) {
// Known to not have write barrier. Store the whole type. // Known to not have write barrier. Store the whole type.

View File

@ -50,8 +50,12 @@ func msanmalloc(addr unsafe.Pointer, sz uintptr)
//go:noescape //go:noescape
func msanfree(addr unsafe.Pointer, sz uintptr) func msanfree(addr unsafe.Pointer, sz uintptr)
// These are called from msan_amd64.s //go:noescape
func msanmove(dst, src unsafe.Pointer, sz uintptr)
// These are called from msan_GOARCH.s
//go:cgo_import_static __msan_read_go //go:cgo_import_static __msan_read_go
//go:cgo_import_static __msan_write_go //go:cgo_import_static __msan_write_go
//go:cgo_import_static __msan_malloc_go //go:cgo_import_static __msan_malloc_go
//go:cgo_import_static __msan_free_go //go:cgo_import_static __msan_free_go
//go:cgo_import_static __msan_memmove

View File

@ -58,6 +58,15 @@ TEXT runtime·msanfree(SB), NOSPLIT, $0-16
MOVQ $__msan_free_go(SB), AX MOVQ $__msan_free_go(SB), AX
JMP msancall<>(SB) JMP msancall<>(SB)
// func runtime·msanmove(dst, src unsafe.Pointer, sz uintptr)
TEXT runtime·msanmove(SB), NOSPLIT, $0-24
MOVQ dst+0(FP), RARG0
MOVQ src+8(FP), RARG1
MOVQ size+16(FP), RARG2
// void __msan_memmove(void *dst, void *src, uintptr_t sz);
MOVQ $__msan_memmove(SB), AX
JMP msancall<>(SB)
// Switches SP to g0 stack and calls (AX). Arguments already set. // Switches SP to g0 stack and calls (AX). Arguments already set.
TEXT msancall<>(SB), NOSPLIT, $0-0 TEXT msancall<>(SB), NOSPLIT, $0-0
get_tls(R12) get_tls(R12)

View File

@ -9,6 +9,7 @@
#define RARG0 R0 #define RARG0 R0
#define RARG1 R1 #define RARG1 R1
#define RARG2 R2
#define FARG R3 #define FARG R3
// func runtime·domsanread(addr unsafe.Pointer, sz uintptr) // func runtime·domsanread(addr unsafe.Pointer, sz uintptr)
@ -45,6 +46,15 @@ TEXT runtime·msanfree(SB), NOSPLIT, $0-16
MOVD $__msan_free_go(SB), FARG MOVD $__msan_free_go(SB), FARG
JMP msancall<>(SB) JMP msancall<>(SB)
// func runtime·msanmove(dst, src unsafe.Pointer, sz uintptr)
TEXT runtime·msanmove(SB), NOSPLIT, $0-24
MOVD dst+0(FP), RARG0
MOVD src+8(FP), RARG1
MOVD size+16(FP), RARG2
// void __msan_memmove(void *dst, void *src, uintptr_t sz);
MOVD $__msan_memmove(SB), FARG
JMP msancall<>(SB)
// Switches SP to g0 stack and calls (FARG). Arguments already set. // Switches SP to g0 stack and calls (FARG). Arguments already set.
TEXT msancall<>(SB), NOSPLIT, $0-0 TEXT msancall<>(SB), NOSPLIT, $0-0
MOVD RSP, R19 // callee-saved MOVD RSP, R19 // callee-saved