Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

random magic value using modified cmd/link alternative #628

Merged
merged 8 commits into from
Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ module mvdan.cc/garble
go 1.19

require (
github.com/bluekeyes/go-gitdiff v0.7.0
github.com/frankban/quicktest v1.14.3
github.com/gofrs/flock v0.8.1
pagran marked this conversation as resolved.
Show resolved Hide resolved
github.com/google/go-cmp v0.5.8
github.com/rogpeppe/go-internal v1.9.0
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91
Expand All @@ -15,4 +17,5 @@ require (
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
github.com/bluekeyes/go-gitdiff v0.7.0 h1:w4SrRFcufU0/tEpWx3VurDBAnWfpxsmwS7yWr14meQk=
github.com/bluekeyes/go-gitdiff v0.7.0/go.mod h1:QpfYYO1E0fTVHVZAZKiRjtSGY9823iCdvGXBcEzHGbM=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand All @@ -21,8 +25,10 @@ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
14 changes: 14 additions & 0 deletions hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"fmt"
"go/token"
"go/types"
Expand Down Expand Up @@ -189,6 +190,19 @@ func isUpper(b byte) bool { return 'A' <= b && b <= 'Z' }
func toLower(b byte) byte { return b + ('a' - 'A') }
func toUpper(b byte) byte { return b - ('a' - 'A') }

// magicValue returns random magic value based
// on user specified seed or runtime.GarbleActionID
pagran marked this conversation as resolved.
Show resolved Hide resolved
func magicValue() uint32 {
hasher.Reset()
if !flagSeed.present() {
hasher.Write(cache.ListedPackages["runtime"].GarbleActionID)
} else {
hasher.Write(flagSeed.bytes)
}
sum := hasher.Sum(sumBuffer[:0])
return binary.LittleEndian.Uint32(sum)
}

func hashWithPackage(pkg *listedPackage, name string) string {
if !flagSeed.present() {
return hashWithCustomSalt(pkg.GarbleActionID, name)
Expand Down
263 changes: 263 additions & 0 deletions internal/linker/linker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// Copyright (c) 2022, The Garble Authors.
// See LICENSE for licensing information.

package linker

import (
"bytes"
"crypto/sha256"
"embed"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/bluekeyes/go-gitdiff/gitdiff"
"github.com/gofrs/flock"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)

const (
MagicValueEnv = "GARBLE_LNK_MAGIC"
pagran marked this conversation as resolved.
Show resolved Hide resolved

cacheDirName = ".garble"
pagran marked this conversation as resolved.
Show resolved Hide resolved
versionExt = ".version"
garbleCacheDir = "GARBLE_CACHE_DIR"
)

var (
//go:embed patches/*.patch
linkerPatchesFs embed.FS
pagran marked this conversation as resolved.
Show resolved Hide resolved

linkerPatchesVer string
linkerPatches map[string]string
pagran marked this conversation as resolved.
Show resolved Hide resolved

baseSrcSubdir = filepath.Join("src", "cmd")
pagran marked this conversation as resolved.
Show resolved Hide resolved
)

func init() {
pagran marked this conversation as resolved.
Show resolved Hide resolved
tmpVer, tmpPatch, err := loadPatches(linkerPatchesFs)
if err != nil {
panic(fmt.Errorf("cannot retrieve patches info: %v", err))
}
linkerPatchesVer = tmpVer
linkerPatches = tmpPatch
}

func loadPatches(patchesFs fs.FS) (string, map[string]string, error) {
versionHash := sha256.New()
patches := make(map[string]string)
err := fs.WalkDir(patchesFs, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
f, err := patchesFs.Open(path)
if err != nil {
return err
}
defer f.Close()

var patchBuf bytes.Buffer
if _, err := io.Copy(&patchBuf, f); err != nil {
return err
}

patchBytes := patchBuf.Bytes()
pagran marked this conversation as resolved.
Show resolved Hide resolved

if _, err := versionHash.Write(patchBytes); err != nil {
return err
}

files, _, err := gitdiff.Parse(bytes.NewReader(patchBytes))
if err != nil {
return err
}
for _, file := range files {
if file.IsDelete || file.IsRename {
pagran marked this conversation as resolved.
Show resolved Hide resolved
panic("delete and rename patch not supported")
}

patches[file.OldName] = string(patchBytes)
}
return nil
})

if err != nil {
return "", nil, err
}
return base64.RawStdEncoding.EncodeToString(versionHash.Sum(nil)), patches, nil
}

func applyPatch(workingDirectory, patch string) error {
pagran marked this conversation as resolved.
Show resolved Hide resolved
cmd := exec.Command("git", "-C", workingDirectory, "apply")
cmd.Stdin = strings.NewReader(patch)
return cmd.Run()
}

func copyFile(src, target string) error {
targetDir := filepath.Dir(target)
if err := os.MkdirAll(targetDir, os.ModePerm); err != nil {
pagran marked this conversation as resolved.
Show resolved Hide resolved
return err
}
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()

targetFile, err := os.Create(target)
if err != nil {
return err
}
defer targetFile.Close()
_, err = io.Copy(targetFile, srcFile)
return err
}

func existsFile(path string) bool {
pagran marked this conversation as resolved.
Show resolved Hide resolved
stat, err := os.Stat(path)
if err != nil {
return false
}
return !stat.IsDir()
}

func applyPatches(srcDirectory, workingDirectory string) (map[string]string, error) {
mod := make(map[string]string)
for fileName, patch := range linkerPatches {
oldPath := filepath.Join(srcDirectory, fileName)
newPath := filepath.Join(workingDirectory, fileName)
mod[oldPath] = newPath

if err := copyFile(oldPath, newPath); err != nil {
return nil, err
}

if err := applyPatch(workingDirectory, patch); err != nil {
return nil, fmt.Errorf("apply patch for %s failed: %v", fileName, err)
}
}
return mod, nil
}

func cachePath(goExe string) (string, error) {
var cacheDir string
if val, ok := os.LookupEnv(garbleCacheDir); ok {
cacheDir = val
} else {
userCacheDir, err := os.UserCacheDir()
if err != nil {
panic(fmt.Errorf("cannot retreive user cache directory: %v", err))
}
cacheDir = userCacheDir
}

cacheDir = filepath.Join(cacheDir, cacheDirName)
if err := os.MkdirAll(cacheDir, os.ModePerm); err != nil {
pagran marked this conversation as resolved.
Show resolved Hide resolved
return "", err
}

return filepath.Join(cacheDir, "link"+goExe), nil
pagran marked this conversation as resolved.
Show resolved Hide resolved
}

func getCurrentVersion(goVersion string) string {
return linkerPatchesVer + " " + goVersion
pagran marked this conversation as resolved.
Show resolved Hide resolved
}

func checkVersion(linkerPath, goVersion string) (bool, error) {
versionPath := linkerPath + versionExt
version, err := os.ReadFile(versionPath)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, err
}

return string(version) == getCurrentVersion(goVersion), nil
}

func writeVersion(linkerPath, goVersion string) error {
versionPath := linkerPath + versionExt
return os.WriteFile(versionPath, []byte(getCurrentVersion(goVersion)), os.ModePerm)
}

type overlayFile struct {
Replace map[string]string
}

func compileLinker(workingDirectory string, overlay map[string]string, outputLinkPath string) error {
mvdan marked this conversation as resolved.
Show resolved Hide resolved
file, err := json.Marshal(&overlayFile{Replace: overlay})
pagran marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
overlayPath := filepath.Join(workingDirectory, "overlay.json")
if err := os.WriteFile(overlayPath, file, os.ModePerm); err != nil {
return err
}

cmd := exec.Command("go", "build", "-overlay", overlayPath, "-o", outputLinkPath, "cmd/link")
// Explicitly setting GOOS and GOARCH variables prevents conflicts during cross-build
cmd.Env = append(os.Environ(), "GOOS="+runtime.GOOS, "GOARCH="+runtime.GOARCH)

out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("compiler compile error: %v\n\n%s", err, string(out))
}

return nil
}

func GetModifiedLinker(goRoot, goVersion, goExe, tempDirectory string) (string, error) {
pagran marked this conversation as resolved.
Show resolved Hide resolved
outputLinkPath, err := cachePath(goExe)
if err != nil {
return "", err
}

// Used for double check. Quick check before filelock and safe check after filelock.
checkLinker := func() (bool, error) {
isCorrectVer, err := checkVersion(outputLinkPath, goVersion)
if err != nil {
return false, err
}
return isCorrectVer && existsFile(outputLinkPath), nil
}

isCorrectVer, err := checkLinker()
if isCorrectVer || err != nil {
return outputLinkPath, err
}

fileLock := flock.New(outputLinkPath + ".lock")
if err := fileLock.Lock(); err != nil {
return "", err
}
defer fileLock.Unlock()

isCorrectVer, err = checkLinker()
if isCorrectVer || err != nil {
return outputLinkPath, err
}
pagran marked this conversation as resolved.
Show resolved Hide resolved

srcDir := filepath.Join(goRoot, baseSrcSubdir)
workingDirectory := filepath.Join(tempDirectory, "linker-src")

overlay, err := applyPatches(srcDir, workingDirectory)
if err != nil {
return "", err
}
if err := compileLinker(workingDirectory, overlay, outputLinkPath); err != nil {
return "", err
}
if err := writeVersion(outputLinkPath, goVersion); err != nil {
return "", err
}
return outputLinkPath, nil
}
20 changes: 20 additions & 0 deletions internal/linker/patches/00-dynamic-magic-value.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
diff --git a/link/internal/ld/pcln.go b/link/internal/ld/pcln.go
--- a/link/internal/ld/pcln.go
+++ b/link/internal/ld/pcln.go
@@ -239,6 +239,16 @@
if off != size {
panic(fmt.Sprintf("pcHeader size: %d != %d", off, size))
}
+
+ if val, ok := os.LookupEnv("GARBLE_LNK_MAGIC"); ok {
+ var magicVal uint32
+ // Use fmt package instead of strconv to avoid importing a new package
+ if _, err := fmt.Sscan(val, &magicVal); err != nil {
+ panic(fmt.Errorf("[garble] invalid magic value %s: %v", val, err))
+ }
+
+ header.SetUint32(ctxt.Arch, 0, magicVal)
+ }
}

state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader)
Loading