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:
parent
d0c0dc682c
commit
07cba70d57
@ -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 {
|
||||||
|
38
misc/cgo/testsanitizers/testdata/msan7.go
vendored
Normal file
38
misc/cgo/testsanitizers/testdata/msan7.go
vendored
Normal 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))
|
||||||
|
}
|
@ -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[:]
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -309,6 +309,7 @@ var (
|
|||||||
growslice,
|
growslice,
|
||||||
msanread,
|
msanread,
|
||||||
msanwrite,
|
msanwrite,
|
||||||
|
msanmove,
|
||||||
newobject,
|
newobject,
|
||||||
newproc,
|
newproc,
|
||||||
panicdivide,
|
panicdivide,
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user