add runtime API to suppress printing fatal errors

Fixes #50.
pull/53/head
Andrew LeFevre 4 years ago committed by GitHub
parent baae7a46fd
commit 7ede37cc0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -29,6 +29,8 @@ order to:
* Strip filenames and shuffle position information
* Obfuscate literals, if the `-literals` flag is given
* Strip debugging information and symbol tables
* Expose additional functions in the runtime that can optionally hide
information during execution
### Options
@ -63,3 +65,33 @@ to document the current shortcomings of this tool.
* Since `garble` forces `-trimpath`, plugins built with `-garble` must be loaded
from Go programs built with `-trimpath` too.
### Runtime API
The tool adds additional functions to the runtime that can optionally be used to
hide information during execution. The funcions added are:
```go
// HidePanics suppresses printing fatal panic messages when hide
// is true. This behavior can be changed at any time by calling
// HidePanics again. All other behaviors of panics remains the
// same.
func HidePanics(hide bool)
```
These functions must be used with the `linkname` compiler directive, like so:
```go
package main
import _ "unsafe"
//go:linkname hideFatalErrors runtime.hideFatalErrors
func hideFatalErrors(hide bool)
func init() { hideFatalErrors(true) }
func main() {
panic("ya like jazz?")
}
```

@ -327,10 +327,11 @@ func transformCompile(args []string) ([]string, error) {
return args, nil
}
pkgPath := flagValue(flags, "-p")
if pkgPath == "runtime/internal/sys" {
// Even though this package isn't private, we will still process
// it to remove the go version constant later. However, we only
// want flags to work on private packages.
if pkgPath == "runtime" || pkgPath == "runtime/internal/sys" {
// Even though these packages aren't private, we will still process
// them later to remove build information and add additional
// functions to the runtime. However, we only want flags to work on
// private packages.
envGarbleLiterals = false
envGarbleDebugDir = ""
} else if !isPrivate(pkgPath) {
@ -356,13 +357,14 @@ func transformCompile(args []string) ([]string, error) {
if err := readBuildIDs(flags); err != nil {
return nil, err
}
// log.Printf("%#v", ids)
var files []*ast.File
for _, path := range paths {
file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
return nil, err
}
files = append(files, file)
}
@ -427,6 +429,9 @@ func transformCompile(args []string) ([]string, error) {
origName := filepath.Base(filepath.Clean(paths[i]))
name := origName
switch {
case pkgPath == "runtime":
// Add additional runtime API
addRuntimeAPI(origName, file)
case pkgPath == "runtime/internal/sys":
// The first declaration in zversion.go contains the Go
// version as follows. Replace it here, since the

@ -0,0 +1,177 @@
package main
import (
"go/ast"
"go/token"
)
// addRuntimeAPI exposes additional functions in the runtime
// package that may be helpful when hiding information
// during execution is required.
func addRuntimeAPI(filename string, file *ast.File) {
switchPanicPrints := func(node ast.Node) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return true
}
id, ok := call.Fun.(*ast.Ident)
if !ok {
return true
}
if id.Name == "print" {
id.Name = "panicprint"
return false
}
return true
}
switch filename {
case "debug.go":
// Add hideFatalErrors function and internal fatalErrorsHidden variable
file.Decls = append(file.Decls, hideFatalErrorsDecls...)
case "error.go":
// Add a function panicprint, that does nothing if panics are
// hidden, otherwise forwards arguments to printany to print them
// as normal. Although we add an if statement to printany to do
// nothing if panics are hidden, printany only takes one argument,
// and both print and printany are used to print panic messages.
// panicprint's entire purpose is to act as a replacement to print
// that respects hideFatalErrors, and print is variadic, so print
// must be replaced by a variadic function, hence panicprint.
//
// We will also add two statements to printany:
// 1. An if statement that returns early if panics are hidden
// 2. An additional case statement that handles printing runtime.hex
// values. Without this case statement, the default case will print
// the runtime.hex values in a way not consistent with normal panic
// outputs
for _, decl := range file.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
if ok && funcDecl.Name.Name == "printany" {
for _, stmt := range funcDecl.Body.List {
switchStmt, ok := stmt.(*ast.TypeSwitchStmt)
if !ok {
continue
}
switchStmt.Body.List = append(switchStmt.Body.List, printanyHexCase)
break
}
funcDecl.Body.List = append([]ast.Stmt{fatalErrorsHiddenCheckStmt}, funcDecl.Body.List...)
break
}
}
file.Decls = append(file.Decls, panicprintDecl)
default:
// Change all calls to print, which we don't control, to
// panicprint, which we do control and does the same thing.
ast.Inspect(file, switchPanicPrints)
}
}
var fatalErrorsHiddenCheckStmt = &ast.IfStmt{
Cond: &ast.Ident{Name: "fatalErrorsHidden"},
Body: &ast.BlockStmt{List: []ast.Stmt{
&ast.ReturnStmt{},
}},
}
var hideFatalErrorsDecls = []ast.Decl{
&ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{
&ast.ValueSpec{
Names: []*ast.Ident{
{Name: "fatalErrorsHidden"},
},
Type: &ast.Ident{Name: "bool"},
},
},
},
&ast.FuncDecl{
Name: &ast.Ident{Name: "hideFatalErrors"},
Type: &ast.FuncType{
Params: &ast.FieldList{
List: []*ast.Field{
{
Names: []*ast.Ident{
{Name: "hide"},
},
Type: &ast.Ident{Name: "bool"},
},
},
},
},
Body: &ast.BlockStmt{
List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{
&ast.Ident{Name: "fatalErrorsHidden"},
},
Tok: token.ASSIGN,
Rhs: []ast.Expr{
&ast.Ident{Name: "hide"},
},
},
},
},
},
}
var printanyHexCase = &ast.CaseClause{
List: []ast.Expr{
&ast.Ident{Name: "hex"},
},
Body: []ast.Stmt{
&ast.ExprStmt{
X: &ast.CallExpr{
Fun: &ast.Ident{Name: "print"},
Args: []ast.Expr{
&ast.Ident{Name: "v"},
},
},
},
},
}
var panicprintDecl = &ast.FuncDecl{
Name: &ast.Ident{Name: "panicprint"},
Type: &ast.FuncType{Params: &ast.FieldList{
List: []*ast.Field{
{
Names: []*ast.Ident{
{Name: "args"},
},
Type: &ast.Ellipsis{Elt: &ast.InterfaceType{
Methods: &ast.FieldList{},
}},
},
},
}},
Body: &ast.BlockStmt{List: []ast.Stmt{
&ast.IfStmt{
Cond: &ast.Ident{Name: "fatalErrorsHidden"},
Body: &ast.BlockStmt{List: []ast.Stmt{
&ast.ReturnStmt{},
}},
},
&ast.RangeStmt{
Key: &ast.Ident{Name: "_"},
Value: &ast.Ident{Name: "arg"},
Tok: token.DEFINE,
X: &ast.Ident{Name: "args"},
Body: &ast.BlockStmt{List: []ast.Stmt{
&ast.ExprStmt{X: &ast.CallExpr{
Fun: &ast.Ident{Name: "printany"},
Args: []ast.Expr{
&ast.Ident{Name: "arg"},
},
}},
}},
},
}},
}

@ -0,0 +1,21 @@
garble build
! exec ./main
cmp stderr main.stderr
-- go.mod --
module test/main
-- main.go --
package main
import _ "unsafe"
//go:linkname hideFatalErrors runtime.hideFatalErrors
func hideFatalErrors(hide bool)
func init() { hideFatalErrors(true) }
func main() {
panic("ya like jazz?")
}
-- main.stderr --
Loading…
Cancel
Save