cmd/asm, cmd/compile, runtime: add -spectre=ret mode

This commit extends the -spectre flag to cmd/asm and adds
a new Spectre mitigation mode "ret", which enables the use
of retpolines.

Retpolines prevent speculation about the target of an indirect
jump or call and are described in more detail here:
https://support.google.com/faqs/answer/7625886

Change-Id: I4f2cb982fa94e44d91e49bd98974fd125619c93a
Reviewed-on: https://go-review.googlesource.com/c/go/+/222661
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Russ Cox 2020-01-17 13:54:30 -05:00
parent 877ef86bec
commit fc8a6336d1
13 changed files with 119 additions and 1 deletions

View file

@ -24,6 +24,8 @@ var (
AllErrors = flag.Bool("e", false, "no limit on number of errors reported")
SymABIs = flag.Bool("gensymabis", false, "write symbol ABI information to output file, don't assemble")
Newobj = flag.Bool("newobj", false, "use new object file format")
Spectre = flag.String("spectre", "", "enable spectre mitigations in `list` (all, ret)")
)
var (

View file

@ -41,6 +41,19 @@ func main() {
ctxt.Flag_dynlink = *flags.Dynlink
ctxt.Flag_shared = *flags.Shared || *flags.Dynlink
ctxt.Flag_newobj = *flags.Newobj
switch *flags.Spectre {
default:
log.Printf("unknown setting -spectre=%s", *flags.Spectre)
os.Exit(2)
case "":
// nothing
case "index":
// known to compiler; ignore here so people can use
// the same list with -gcflags=-spectre=LIST and -asmflags=-spectrre=LIST
case "all", "ret":
ctxt.Retpoline = true
}
ctxt.Bso = bufio.NewWriter(os.Stdout)
defer ctxt.Bso.Flush()

View file

@ -294,8 +294,11 @@ func Main(archInit func(*Arch)) {
// nothing
case "all":
spectreIndex = true
Ctxt.Retpoline = true
case "index":
spectreIndex = true
case "ret":
Ctxt.Retpoline = true
}
}

View file

@ -379,6 +379,11 @@ func checkSuffix(c *ctxt5, p *obj.Prog, o *Optab) {
}
func span5(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
if ctxt.Retpoline {
ctxt.Diag("-spectre=ret not supported on arm")
ctxt.Retpoline = false // don't keep printing
}
var p *obj.Prog
var op *obj.Prog

View file

@ -881,6 +881,11 @@ var prfopfield = []struct {
}
func span7(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
if ctxt.Retpoline {
ctxt.Diag("-spectre=ret not supported on arm64")
ctxt.Retpoline = false // don't keep printing
}
p := cursym.Func.Text
if p == nil || p.Link == nil { // handle external functions and ELF section symbols
return

View file

@ -653,6 +653,7 @@ type Link struct {
Flag_optimize bool
Flag_locationlists bool
Flag_newobj bool // use new object file format
Retpoline bool // emit use of retpoline stubs for indirect jmp/call
Bso *bufio.Writer
Pathname string
hashmu sync.Mutex // protects hash, funchash

View file

@ -402,6 +402,11 @@ var oprange [ALAST & obj.AMask][]Optab
var xcmp [C_NCLASS][C_NCLASS]bool
func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
if ctxt.Retpoline {
ctxt.Diag("-spectre=ret not supported on mips")
ctxt.Retpoline = false // don't keep printing
}
p := cursym.Func.Text
if p == nil || p.Link == nil { // handle external functions and ELF section symbols
return

View file

@ -1835,6 +1835,11 @@ func encodingForProg(p *obj.Prog) encoding {
// assemble emits machine code.
// It is called at the very end of the assembly process.
func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
if ctxt.Retpoline {
ctxt.Diag("-spectre=ret not supported on riscv")
ctxt.Retpoline = false // don't keep printing
}
var symcode []uint32
for p := cursym.Func.Text; p != nil; p = p.Link {
switch p.As {

View file

@ -442,6 +442,11 @@ var oprange [ALAST & obj.AMask][]Optab
var xcmp [C_NCLASS][C_NCLASS]bool
func spanz(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
if ctxt.Retpoline {
ctxt.Diag("-spectre=ret not supported on s390x")
ctxt.Retpoline = false // don't keep printing
}
p := cursym.Func.Text
if p == nil || p.Link == nil { // handle external functions and ELF section symbols
return

View file

@ -1875,6 +1875,17 @@ func span6(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
p.As = spadjop(ctxt, ASUBL, ASUBQ)
}
}
if ctxt.Retpoline && (p.As == obj.ACALL || p.As == obj.AJMP) && (p.To.Type == obj.TYPE_REG || p.To.Type == obj.TYPE_MEM) {
if p.To.Type != obj.TYPE_REG {
ctxt.Diag("non-retpoline-compatible: %v", p)
continue
}
p.To.Type = obj.TYPE_BRANCH
p.To.Name = obj.NAME_EXTERN
p.To.Sym = ctxt.Lookup("runtime.retpoline" + obj.Rconv(int(p.To.Reg)))
p.To.Reg = 0
p.To.Offset = 0
}
}
var count int64 // rough count of number of instructions

View file

@ -510,7 +510,8 @@ TEXT NAME(SB), WRAPPER, $MAXSIZE-32; \
/* call function */ \
MOVQ f+8(FP), DX; \
PCDATA $PCDATA_StackMapIndex, $0; \
CALL (DX); \
MOVQ (DX), AX; \
CALL AX; \
/* copy return values back */ \
MOVQ argtype+0(FP), DX; \
MOVQ argptr+16(FP), DI; \
@ -1743,3 +1744,34 @@ TEXT runtime·panicSlice3CU(SB),NOSPLIT,$0-16
DATA runtime·tls_g+0(SB)/8, $16
GLOBL runtime·tls_g+0(SB), NOPTR, $8
#endif
// The compiler and assembler's -spectre=ret mode rewrites
// all indirect CALL AX / JMP AX instructions to be
// CALL retpolineAX / JMP retpolineAX.
// See https://support.google.com/faqs/answer/7625886.
#define RETPOLINE(reg) \
/* CALL setup */ BYTE $0xE8; BYTE $(2+2); BYTE $0; BYTE $0; BYTE $0; \
/* nospec: */ \
/* PAUSE */ BYTE $0xF3; BYTE $0x90; \
/* JMP nospec */ BYTE $0xEB; BYTE $-(2+2); \
/* setup: */ \
/* MOVQ AX, 0(SP) */ BYTE $0x48|((reg&8)>>1); BYTE $0x89; \
BYTE $0x04|((reg&7)<<3); BYTE $0x24; \
/* RET */ BYTE $0xC3
TEXT runtime·retpolineAX(SB),NOSPLIT,$0; RETPOLINE(0)
TEXT runtime·retpolineCX(SB),NOSPLIT,$0; RETPOLINE(1)
TEXT runtime·retpolineDX(SB),NOSPLIT,$0; RETPOLINE(2)
TEXT runtime·retpolineBX(SB),NOSPLIT,$0; RETPOLINE(3)
/* SP is 4, can't happen / magic encodings */
TEXT runtime·retpolineBP(SB),NOSPLIT,$0; RETPOLINE(5)
TEXT runtime·retpolineSI(SB),NOSPLIT,$0; RETPOLINE(6)
TEXT runtime·retpolineDI(SB),NOSPLIT,$0; RETPOLINE(7)
TEXT runtime·retpolineR8(SB),NOSPLIT,$0; RETPOLINE(8)
TEXT runtime·retpolineR9(SB),NOSPLIT,$0; RETPOLINE(9)
TEXT runtime·retpolineR10(SB),NOSPLIT,$0; RETPOLINE(10)
TEXT runtime·retpolineR11(SB),NOSPLIT,$0; RETPOLINE(11)
TEXT runtime·retpolineR12(SB),NOSPLIT,$0; RETPOLINE(12)
TEXT runtime·retpolineR13(SB),NOSPLIT,$0; RETPOLINE(13)
TEXT runtime·retpolineR14(SB),NOSPLIT,$0; RETPOLINE(14)
TEXT runtime·retpolineR15(SB),NOSPLIT,$0; RETPOLINE(15)

View file

@ -9,3 +9,20 @@ func stackcheck()
// Called from assembly only; declared for go vet.
func settls() // argument in DI
// Retpolines, used by -spectre=ret flag in cmd/asm, cmd/compile.
func retpolineAX()
func retpolineCX()
func retpolineDX()
func retpolineBX()
func retpolineBP()
func retpolineSI()
func retpolineDI()
func retpolineR8()
func retpolineR9()
func retpolineR10()
func retpolineR11()
func retpolineR12()
func retpolineR13()
func retpolineR14()
func retpolineR15()

14
test/codegen/retpoline.go Normal file
View file

@ -0,0 +1,14 @@
// +build amd64
// asmcheck -gcflags=-spectre=ret
package codegen
func CallFunc(f func()) {
// amd64:`CALL\truntime.retpoline`
f()
}
func CallInterface(x interface{ M() }) {
// amd64:`CALL\truntime.retpoline`
x.M()
}