main.go 74 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389
  1. // Copyright (c) 2019, The Garble Authors.
  2. // See LICENSE for licensing information.
  3. package main
  4. import (
  5. "bufio"
  6. "bytes"
  7. cryptorand "crypto/rand"
  8. "encoding/base64"
  9. "encoding/binary"
  10. "encoding/gob"
  11. "encoding/json"
  12. "errors"
  13. "flag"
  14. "fmt"
  15. "go/ast"
  16. "go/importer"
  17. "go/parser"
  18. "go/token"
  19. "go/types"
  20. "io"
  21. "io/fs"
  22. "log"
  23. mathrand "math/rand"
  24. "os"
  25. "os/exec"
  26. "path/filepath"
  27. "regexp"
  28. "runtime"
  29. "runtime/debug"
  30. "strconv"
  31. "strings"
  32. "time"
  33. "unicode"
  34. "unicode/utf8"
  35. "github.com/rogpeppe/go-internal/cache"
  36. "golang.org/x/exp/maps"
  37. "golang.org/x/exp/slices"
  38. "golang.org/x/mod/module"
  39. "golang.org/x/mod/semver"
  40. "golang.org/x/tools/go/ast/astutil"
  41. "golang.org/x/tools/go/ssa"
  42. "mvdan.cc/garble/internal/ctrlflow"
  43. "mvdan.cc/garble/internal/linker"
  44. "mvdan.cc/garble/internal/literals"
  45. )
  46. var flagSet = flag.NewFlagSet("garble", flag.ContinueOnError)
  47. var (
  48. flagLiterals bool
  49. flagTiny bool
  50. flagDebug bool
  51. flagDebugDir string
  52. flagSeed seedFlag
  53. // TODO(pagran): in the future, when control flow obfuscation will be stable migrate to flag
  54. flagControlFlow = os.Getenv("GARBLE_EXPERIMENTAL_CONTROLFLOW") == "1"
  55. )
  56. func init() {
  57. flagSet.Usage = usage
  58. flagSet.BoolVar(&flagLiterals, "literals", false, "Obfuscate literals such as strings")
  59. flagSet.BoolVar(&flagTiny, "tiny", false, "Optimize for binary size, losing some ability to reverse the process")
  60. flagSet.BoolVar(&flagDebug, "debug", false, "Print debug logs to stderr")
  61. flagSet.StringVar(&flagDebugDir, "debugdir", "", "Write the obfuscated source to a directory, e.g. -debugdir=out")
  62. flagSet.Var(&flagSeed, "seed", "Provide a base64-encoded seed, e.g. -seed=o9WDTZ4CN4w\nFor a random seed, provide -seed=random")
  63. }
  64. var rxGarbleFlag = regexp.MustCompile(`-(?:literals|tiny|debug|debugdir|seed)(?:$|=)`)
  65. type seedFlag struct {
  66. random bool
  67. bytes []byte
  68. }
  69. func (f seedFlag) present() bool { return len(f.bytes) > 0 }
  70. func (f seedFlag) String() string {
  71. return base64.RawStdEncoding.EncodeToString(f.bytes)
  72. }
  73. func (f *seedFlag) Set(s string) error {
  74. if s == "random" {
  75. f.random = true // to show the random seed we chose
  76. f.bytes = make([]byte, 16) // random 128 bit seed
  77. if _, err := cryptorand.Read(f.bytes); err != nil {
  78. return fmt.Errorf("error generating random seed: %v", err)
  79. }
  80. } else {
  81. // We expect unpadded base64, but to be nice, accept padded
  82. // strings too.
  83. s = strings.TrimRight(s, "=")
  84. seed, err := base64.RawStdEncoding.DecodeString(s)
  85. if err != nil {
  86. return fmt.Errorf("error decoding seed: %v", err)
  87. }
  88. // TODO: Note that we always use 8 bytes; any bytes after that are
  89. // entirely ignored. That may be confusing to the end user.
  90. if len(seed) < 8 {
  91. return fmt.Errorf("-seed needs at least 8 bytes, have %d", len(seed))
  92. }
  93. f.bytes = seed
  94. }
  95. return nil
  96. }
  97. func usage() {
  98. fmt.Fprintf(os.Stderr, `
  99. Garble obfuscates Go code by wrapping the Go toolchain.
  100. garble [garble flags] command [go flags] [go arguments]
  101. For example, to build an obfuscated program:
  102. garble build ./cmd/foo
  103. Similarly, to combine garble flags and Go build flags:
  104. garble -literals build -tags=purego ./cmd/foo
  105. The following commands are supported:
  106. build replace "go build"
  107. test replace "go test"
  108. run replace "go run"
  109. reverse de-obfuscate output such as stack traces
  110. version print the version and build settings of the garble binary
  111. To learn more about a command, run "garble help <command>".
  112. garble accepts the following flags before a command:
  113. `[1:])
  114. flagSet.PrintDefaults()
  115. fmt.Fprintf(os.Stderr, `
  116. For more information, see https://github.com/burrowers/garble.
  117. `[1:])
  118. }
  119. func main() { os.Exit(main1()) }
  120. var (
  121. // Presumably OK to share fset across packages.
  122. fset = token.NewFileSet()
  123. sharedTempDir = os.Getenv("GARBLE_SHARED")
  124. parentWorkDir = os.Getenv("GARBLE_PARENT_WORK")
  125. )
  126. const actionGraphFileName = "action-graph.json"
  127. type importerWithMap struct {
  128. importMap map[string]string
  129. importFrom func(path, dir string, mode types.ImportMode) (*types.Package, error)
  130. }
  131. func (im importerWithMap) Import(path string) (*types.Package, error) {
  132. panic("should never be called")
  133. }
  134. func (im importerWithMap) ImportFrom(path, dir string, mode types.ImportMode) (*types.Package, error) {
  135. if path2 := im.importMap[path]; path2 != "" {
  136. path = path2
  137. }
  138. return im.importFrom(path, dir, mode)
  139. }
  140. func importerForPkg(lpkg *listedPackage) importerWithMap {
  141. return importerWithMap{
  142. importFrom: importer.ForCompiler(fset, "gc", func(path string) (io.ReadCloser, error) {
  143. pkg, err := listPackage(lpkg, path)
  144. if err != nil {
  145. return nil, err
  146. }
  147. return os.Open(pkg.Export)
  148. }).(types.ImporterFrom).ImportFrom,
  149. importMap: lpkg.ImportMap,
  150. }
  151. }
  152. // uniqueLineWriter sits underneath log.SetOutput to deduplicate log lines.
  153. // We log bits of useful information for debugging,
  154. // and logging the same detail twice is not going to help the user.
  155. // Duplicates are relatively normal, given that names tend to repeat.
  156. type uniqueLineWriter struct {
  157. out io.Writer
  158. seen map[string]bool
  159. }
  160. func (w *uniqueLineWriter) Write(p []byte) (n int, err error) {
  161. if !flagDebug {
  162. panic("unexpected use of uniqueLineWriter with -debug unset")
  163. }
  164. if bytes.Count(p, []byte("\n")) != 1 {
  165. panic(fmt.Sprintf("log write wasn't just one line: %q", p))
  166. }
  167. if w.seen[string(p)] {
  168. return len(p), nil
  169. }
  170. if w.seen == nil {
  171. w.seen = make(map[string]bool)
  172. }
  173. w.seen[string(p)] = true
  174. return w.out.Write(p)
  175. }
  176. // debugSince is like time.Since but resulting in shorter output.
  177. // A build process takes at least hundreds of milliseconds,
  178. // so extra decimal points in the order of microseconds aren't meaningful.
  179. func debugSince(start time.Time) time.Duration {
  180. return time.Since(start).Truncate(10 * time.Microsecond)
  181. }
  182. func main1() int {
  183. defer func() {
  184. if os.Getenv("GARBLE_WRITE_ALLOCS") != "true" {
  185. return
  186. }
  187. var memStats runtime.MemStats
  188. runtime.ReadMemStats(&memStats)
  189. fmt.Fprintf(os.Stderr, "garble allocs: %d\n", memStats.Mallocs)
  190. }()
  191. if err := flagSet.Parse(os.Args[1:]); err != nil {
  192. return 2
  193. }
  194. log.SetPrefix("[garble] ")
  195. log.SetFlags(0) // no timestamps, as they aren't very useful
  196. if flagDebug {
  197. // TODO: cover this in the tests.
  198. log.SetOutput(&uniqueLineWriter{out: os.Stderr})
  199. } else {
  200. log.SetOutput(io.Discard)
  201. }
  202. args := flagSet.Args()
  203. if len(args) < 1 {
  204. usage()
  205. return 2
  206. }
  207. // If a random seed was used, the user won't be able to reproduce the
  208. // same output or failure unless we print the random seed we chose.
  209. // If the build failed and a random seed was used,
  210. // the failure might not reproduce with a different seed.
  211. // Print it before we exit.
  212. if flagSeed.random {
  213. fmt.Fprintf(os.Stderr, "-seed chosen at random: %s\n", base64.RawStdEncoding.EncodeToString(flagSeed.bytes))
  214. }
  215. if err := mainErr(args); err != nil {
  216. if code, ok := err.(errJustExit); ok {
  217. return int(code)
  218. }
  219. fmt.Fprintln(os.Stderr, err)
  220. return 1
  221. }
  222. return 0
  223. }
  224. type errJustExit int
  225. func (e errJustExit) Error() string { return fmt.Sprintf("exit: %d", e) }
  226. func goVersionOK() bool {
  227. const (
  228. minGoVersionSemver = "v1.20.0"
  229. suggestedGoVersion = "1.20.x"
  230. )
  231. // rxVersion looks for a version like "go1.2" or "go1.2.3"
  232. rxVersion := regexp.MustCompile(`go\d+\.\d+(?:\.\d+)?`)
  233. toolchainVersionFull := sharedCache.GoEnv.GOVERSION
  234. toolchainVersion := rxVersion.FindString(toolchainVersionFull)
  235. if toolchainVersion == "" {
  236. // Go 1.15.x and older do not have GOVERSION yet.
  237. // We could go the extra mile and fetch it via 'go toolchainVersion',
  238. // but we'd have to error anyway.
  239. fmt.Fprintf(os.Stderr, "Go version is too old; please upgrade to Go %s or newer\n", suggestedGoVersion)
  240. return false
  241. }
  242. sharedCache.GoVersionSemver = "v" + strings.TrimPrefix(toolchainVersion, "go")
  243. if semver.Compare(sharedCache.GoVersionSemver, minGoVersionSemver) < 0 {
  244. fmt.Fprintf(os.Stderr, "Go version %q is too old; please upgrade to Go %s or newer\n", toolchainVersionFull, suggestedGoVersion)
  245. return false
  246. }
  247. // Ensure that the version of Go that built the garble binary is equal or
  248. // newer than cache.GoVersionSemver.
  249. builtVersionFull := os.Getenv("GARBLE_TEST_GOVERSION")
  250. if builtVersionFull == "" {
  251. builtVersionFull = runtime.Version()
  252. }
  253. builtVersion := rxVersion.FindString(builtVersionFull)
  254. if builtVersion == "" {
  255. // If garble built itself, we don't know what Go version was used.
  256. // Fall back to not performing the check against the toolchain version.
  257. return true
  258. }
  259. builtVersionSemver := "v" + strings.TrimPrefix(builtVersion, "go")
  260. if semver.Compare(builtVersionSemver, sharedCache.GoVersionSemver) < 0 {
  261. fmt.Fprintf(os.Stderr, `
  262. garble was built with %q and is being used with %q; rebuild it with a command like:
  263. go install mvdan.cc/garble@latest
  264. `[1:], builtVersionFull, toolchainVersionFull)
  265. return false
  266. }
  267. return true
  268. }
  269. func mainErr(args []string) error {
  270. command, args := args[0], args[1:]
  271. // Catch users reaching for `go build -toolexec=garble`.
  272. if command != "toolexec" && len(args) == 1 && args[0] == "-V=full" {
  273. return fmt.Errorf(`did you run "go [command] -toolexec=garble" instead of "garble [command]"?`)
  274. }
  275. switch command {
  276. case "help":
  277. if hasHelpFlag(args) || len(args) > 1 {
  278. fmt.Fprintf(os.Stderr, "usage: garble help [command]\n")
  279. return errJustExit(2)
  280. }
  281. if len(args) == 1 {
  282. return mainErr([]string{args[0], "-h"})
  283. }
  284. usage()
  285. return errJustExit(2)
  286. case "version":
  287. if hasHelpFlag(args) || len(args) > 0 {
  288. fmt.Fprintf(os.Stderr, "usage: garble version\n")
  289. return errJustExit(2)
  290. }
  291. info, ok := debug.ReadBuildInfo()
  292. if !ok {
  293. // The build binary was stripped of build info?
  294. // Could be the case if garble built itself.
  295. fmt.Println("unknown")
  296. return nil
  297. }
  298. mod := &info.Main
  299. if mod.Replace != nil {
  300. mod = mod.Replace
  301. }
  302. // For the tests.
  303. if v := os.Getenv("GARBLE_TEST_BUILDSETTINGS"); v != "" {
  304. var extra []debug.BuildSetting
  305. if err := json.Unmarshal([]byte(v), &extra); err != nil {
  306. return err
  307. }
  308. info.Settings = append(info.Settings, extra...)
  309. }
  310. // Until https://github.com/golang/go/issues/50603 is implemented,
  311. // manually construct something like a pseudo-version.
  312. // TODO: remove when this code is dead, hopefully in Go 1.22.
  313. if mod.Version == "(devel)" {
  314. var vcsTime time.Time
  315. var vcsRevision string
  316. for _, setting := range info.Settings {
  317. switch setting.Key {
  318. case "vcs.time":
  319. // If the format is invalid, we'll print a zero timestamp.
  320. vcsTime, _ = time.Parse(time.RFC3339Nano, setting.Value)
  321. case "vcs.revision":
  322. vcsRevision = setting.Value
  323. if len(vcsRevision) > 12 {
  324. vcsRevision = vcsRevision[:12]
  325. }
  326. }
  327. }
  328. if vcsRevision != "" {
  329. mod.Version = module.PseudoVersion("", "", vcsTime, vcsRevision)
  330. }
  331. }
  332. fmt.Printf("%s %s\n\n", mod.Path, mod.Version)
  333. fmt.Printf("Build settings:\n")
  334. for _, setting := range info.Settings {
  335. if setting.Value == "" {
  336. continue // do empty build settings even matter?
  337. }
  338. // The padding helps keep readability by aligning:
  339. //
  340. // veryverylong.key value
  341. // short.key some-other-value
  342. //
  343. // Empirically, 16 is enough; the longest key seen is "vcs.revision".
  344. fmt.Printf("%16s %s\n", setting.Key, setting.Value)
  345. }
  346. return nil
  347. case "reverse":
  348. return commandReverse(args)
  349. case "build", "test", "run":
  350. cmd, err := toolexecCmd(command, args)
  351. defer func() {
  352. if err := os.RemoveAll(os.Getenv("GARBLE_SHARED")); err != nil {
  353. fmt.Fprintf(os.Stderr, "could not clean up GARBLE_SHARED: %v\n", err)
  354. }
  355. // skip the trim if we didn't even start a build
  356. if sharedCache != nil {
  357. fsCache, err := openCache()
  358. if err == nil {
  359. err = fsCache.Trim()
  360. }
  361. if err != nil {
  362. fmt.Fprintf(os.Stderr, "could not trim GARBLE_CACHE: %v\n", err)
  363. }
  364. }
  365. }()
  366. if err != nil {
  367. return err
  368. }
  369. cmd.Stdout = os.Stdout
  370. cmd.Stderr = os.Stderr
  371. log.Printf("calling via toolexec: %s", cmd)
  372. return cmd.Run()
  373. case "toolexec":
  374. _, tool := filepath.Split(args[0])
  375. if runtime.GOOS == "windows" {
  376. tool = strings.TrimSuffix(tool, ".exe")
  377. }
  378. transform := transformMethods[tool]
  379. transformed := args[1:]
  380. if transform != nil {
  381. startTime := time.Now()
  382. log.Printf("transforming %s with args: %s", tool, strings.Join(transformed, " "))
  383. // We're in a toolexec sub-process, not directly called by the user.
  384. // Load the shared data and wrap the tool, like the compiler or linker.
  385. if err := loadSharedCache(); err != nil {
  386. return err
  387. }
  388. if len(args) == 2 && args[1] == "-V=full" {
  389. return alterToolVersion(tool, args)
  390. }
  391. var tf transformer
  392. toolexecImportPath := os.Getenv("TOOLEXEC_IMPORTPATH")
  393. tf.curPkg = sharedCache.ListedPackages[toolexecImportPath]
  394. if tf.curPkg == nil {
  395. return fmt.Errorf("TOOLEXEC_IMPORTPATH not found in listed packages: %s", toolexecImportPath)
  396. }
  397. tf.origImporter = importerForPkg(tf.curPkg)
  398. var err error
  399. if transformed, err = transform(&tf, transformed); err != nil {
  400. return err
  401. }
  402. log.Printf("transformed args for %s in %s: %s", tool, debugSince(startTime), strings.Join(transformed, " "))
  403. } else {
  404. log.Printf("skipping transform on %s with args: %s", tool, strings.Join(transformed, " "))
  405. }
  406. executablePath := args[0]
  407. if tool == "link" {
  408. modifiedLinkPath, unlock, err := linker.PatchLinker(sharedCache.GoEnv.GOROOT, sharedCache.GoEnv.GOVERSION, sharedCache.CacheDir, sharedTempDir)
  409. if err != nil {
  410. return fmt.Errorf("cannot get modified linker: %v", err)
  411. }
  412. defer unlock()
  413. executablePath = modifiedLinkPath
  414. os.Setenv(linker.MagicValueEnv, strconv.FormatUint(uint64(magicValue()), 10))
  415. os.Setenv(linker.EntryOffKeyEnv, strconv.FormatUint(uint64(entryOffKey()), 10))
  416. if flagTiny {
  417. os.Setenv(linker.TinyEnv, "true")
  418. }
  419. log.Printf("replaced linker with: %s", executablePath)
  420. }
  421. cmd := exec.Command(executablePath, transformed...)
  422. cmd.Stdout = os.Stdout
  423. cmd.Stderr = os.Stderr
  424. if err := cmd.Run(); err != nil {
  425. return err
  426. }
  427. return nil
  428. default:
  429. return fmt.Errorf("unknown command: %q", command)
  430. }
  431. }
  432. func hasHelpFlag(flags []string) bool {
  433. for _, f := range flags {
  434. switch f {
  435. case "-h", "-help", "--help":
  436. return true
  437. }
  438. }
  439. return false
  440. }
  441. // toolexecCmd builds an *exec.Cmd which is set up for running "go <command>"
  442. // with -toolexec=garble and the supplied arguments.
  443. //
  444. // Note that it uses and modifies global state; in general, it should only be
  445. // called once from mainErr in the top-level garble process.
  446. func toolexecCmd(command string, args []string) (*exec.Cmd, error) {
  447. // Split the flags from the package arguments, since we'll need
  448. // to run 'go list' on the same set of packages.
  449. flags, args := splitFlagsFromArgs(args)
  450. if hasHelpFlag(flags) {
  451. out, _ := exec.Command("go", command, "-h").CombinedOutput()
  452. fmt.Fprintf(os.Stderr, `
  453. usage: garble [garble flags] %s [arguments]
  454. This command wraps "go %s". Below is its help:
  455. %s`[1:], command, command, out)
  456. return nil, errJustExit(2)
  457. }
  458. for _, flag := range flags {
  459. if rxGarbleFlag.MatchString(flag) {
  460. return nil, fmt.Errorf("garble flags must precede command, like: garble %s build ./pkg", flag)
  461. }
  462. }
  463. // Here is the only place we initialize the cache.
  464. // The sub-processes will parse it from a shared gob file.
  465. sharedCache = &sharedCacheType{}
  466. // Note that we also need to pass build flags to 'go list', such
  467. // as -tags.
  468. sharedCache.ForwardBuildFlags, _ = filterForwardBuildFlags(flags)
  469. if command == "test" {
  470. sharedCache.ForwardBuildFlags = append(sharedCache.ForwardBuildFlags, "-test")
  471. }
  472. if err := fetchGoEnv(); err != nil {
  473. return nil, err
  474. }
  475. if !goVersionOK() {
  476. return nil, errJustExit(1)
  477. }
  478. var err error
  479. sharedCache.ExecPath, err = os.Executable()
  480. if err != nil {
  481. return nil, err
  482. }
  483. // Always an absolute directory; defaults to e.g. "~/.cache/garble".
  484. if dir := os.Getenv("GARBLE_CACHE"); dir != "" {
  485. sharedCache.CacheDir, err = filepath.Abs(dir)
  486. if err != nil {
  487. return nil, err
  488. }
  489. } else {
  490. parentDir, err := os.UserCacheDir()
  491. if err != nil {
  492. return nil, err
  493. }
  494. sharedCache.CacheDir = filepath.Join(parentDir, "garble")
  495. }
  496. binaryBuildID, err := buildidOf(sharedCache.ExecPath)
  497. if err != nil {
  498. return nil, err
  499. }
  500. sharedCache.BinaryContentID = decodeBuildIDHash(splitContentID(binaryBuildID))
  501. if err := appendListedPackages(args, true); err != nil {
  502. return nil, err
  503. }
  504. sharedTempDir, err = saveSharedCache()
  505. if err != nil {
  506. return nil, err
  507. }
  508. os.Setenv("GARBLE_SHARED", sharedTempDir)
  509. wd, err := os.Getwd()
  510. if err != nil {
  511. return nil, err
  512. }
  513. os.Setenv("GARBLE_PARENT_WORK", wd)
  514. if flagDebugDir != "" {
  515. if !filepath.IsAbs(flagDebugDir) {
  516. flagDebugDir = filepath.Join(wd, flagDebugDir)
  517. }
  518. if err := os.RemoveAll(flagDebugDir); err != nil {
  519. return nil, fmt.Errorf("could not empty debugdir: %v", err)
  520. }
  521. if err := os.MkdirAll(flagDebugDir, 0o755); err != nil {
  522. return nil, err
  523. }
  524. }
  525. goArgs := append([]string{command}, garbleBuildFlags...)
  526. // Pass the garble flags down to each toolexec invocation.
  527. // This way, all garble processes see the same flag values.
  528. // Note that we can end up with a single argument to `go` in the form of:
  529. //
  530. // -toolexec='/binary dir/garble' -tiny toolexec
  531. //
  532. // We quote the absolute path to garble if it contains spaces.
  533. // We can add extra flags to the end of the same -toolexec argument.
  534. var toolexecFlag strings.Builder
  535. toolexecFlag.WriteString("-toolexec=")
  536. quotedExecPath, err := cmdgoQuotedJoin([]string{sharedCache.ExecPath})
  537. if err != nil {
  538. // Can only happen if the absolute path to the garble binary contains
  539. // both single and double quotes. Seems extremely unlikely.
  540. return nil, err
  541. }
  542. toolexecFlag.WriteString(quotedExecPath)
  543. appendFlags(&toolexecFlag, false)
  544. toolexecFlag.WriteString(" toolexec")
  545. goArgs = append(goArgs, toolexecFlag.String())
  546. if flagControlFlow {
  547. goArgs = append(goArgs, "-debug-actiongraph", filepath.Join(sharedTempDir, actionGraphFileName))
  548. }
  549. if flagDebugDir != "" {
  550. // In case the user deletes the debug directory,
  551. // and a previous build is cached,
  552. // rebuild all packages to re-fill the debug dir.
  553. goArgs = append(goArgs, "-a")
  554. }
  555. if command == "test" {
  556. // vet is generally not useful on obfuscated code; keep it
  557. // disabled by default.
  558. goArgs = append(goArgs, "-vet=off")
  559. }
  560. goArgs = append(goArgs, flags...)
  561. goArgs = append(goArgs, args...)
  562. return exec.Command("go", goArgs...), nil
  563. }
  564. var transformMethods = map[string]func(*transformer, []string) ([]string, error){
  565. "asm": (*transformer).transformAsm,
  566. "compile": (*transformer).transformCompile,
  567. "link": (*transformer).transformLink,
  568. }
  569. func (tf *transformer) transformAsm(args []string) ([]string, error) {
  570. flags, paths := splitFlagsFromFiles(args, ".s")
  571. // When assembling, the import path can make its way into the output object file.
  572. if tf.curPkg.Name != "main" && tf.curPkg.ToObfuscate {
  573. flags = flagSetValue(flags, "-p", tf.curPkg.obfuscatedImportPath())
  574. }
  575. flags = alterTrimpath(flags)
  576. // The assembler runs twice; the first with -gensymabis,
  577. // where we continue below and we obfuscate all the source.
  578. // The second time, without -gensymabis, we reconstruct the paths to the
  579. // obfuscated source files and reuse them to avoid work.
  580. newPaths := make([]string, 0, len(paths))
  581. if !slices.Contains(args, "-gensymabis") {
  582. for _, path := range paths {
  583. name := hashWithPackage(tf.curPkg, filepath.Base(path)) + ".s"
  584. pkgDir := filepath.Join(sharedTempDir, tf.curPkg.obfuscatedImportPath())
  585. newPath := filepath.Join(pkgDir, name)
  586. newPaths = append(newPaths, newPath)
  587. }
  588. return append(flags, newPaths...), nil
  589. }
  590. const missingHeader = "missing header path"
  591. newHeaderPaths := make(map[string]string)
  592. var buf, includeBuf bytes.Buffer
  593. for _, path := range paths {
  594. buf.Reset()
  595. f, err := os.Open(path)
  596. if err != nil {
  597. return nil, err
  598. }
  599. defer f.Close() // in case of error
  600. scanner := bufio.NewScanner(f)
  601. for scanner.Scan() {
  602. line := scanner.Text()
  603. // First, handle hash directives without leading whitespaces.
  604. // #include "foo.h"
  605. if quoted := strings.TrimPrefix(line, "#include"); quoted != line {
  606. quoted = strings.TrimSpace(quoted)
  607. path, err := strconv.Unquote(quoted)
  608. if err != nil {
  609. return nil, err
  610. }
  611. newPath := newHeaderPaths[path]
  612. switch newPath {
  613. case missingHeader: // no need to try again
  614. buf.WriteString(line)
  615. buf.WriteByte('\n')
  616. continue
  617. case "": // first time we see this header
  618. includeBuf.Reset()
  619. content, err := os.ReadFile(path)
  620. if errors.Is(err, fs.ErrNotExist) {
  621. newHeaderPaths[path] = missingHeader
  622. buf.WriteString(line)
  623. buf.WriteByte('\n')
  624. continue // a header file provided by Go or the system
  625. } else if err != nil {
  626. return nil, err
  627. }
  628. tf.replaceAsmNames(&includeBuf, content)
  629. // For now, we replace `foo.h` or `dir/foo.h` with `garbled_foo.h`.
  630. // The different name ensures we don't use the unobfuscated file.
  631. // This is far from perfect, but does the job for the time being.
  632. // In the future, use a randomized name.
  633. basename := filepath.Base(path)
  634. newPath = "garbled_" + basename
  635. if _, err := tf.writeSourceFile(basename, newPath, includeBuf.Bytes()); err != nil {
  636. return nil, err
  637. }
  638. newHeaderPaths[path] = newPath
  639. }
  640. buf.WriteString("#include ")
  641. buf.WriteString(strconv.Quote(newPath))
  642. buf.WriteByte('\n')
  643. continue
  644. }
  645. // Leave "//" comments unchanged; they might be directives.
  646. line, comment, hasComment := strings.Cut(line, "//")
  647. // Anything else is regular assembly; replace the names.
  648. tf.replaceAsmNames(&buf, []byte(line))
  649. if hasComment {
  650. buf.WriteString("//")
  651. buf.WriteString(comment)
  652. }
  653. buf.WriteByte('\n')
  654. }
  655. if err := scanner.Err(); err != nil {
  656. return nil, err
  657. }
  658. // With assembly files, we obfuscate the filename in the temporary
  659. // directory, as assembly files do not support `/*line` directives.
  660. // TODO(mvdan): per cmd/asm/internal/lex, they do support `#line`.
  661. basename := filepath.Base(path)
  662. newName := hashWithPackage(tf.curPkg, basename) + ".s"
  663. if path, err := tf.writeSourceFile(basename, newName, buf.Bytes()); err != nil {
  664. return nil, err
  665. } else {
  666. newPaths = append(newPaths, path)
  667. }
  668. f.Close() // do not keep len(paths) files open
  669. }
  670. return append(flags, newPaths...), nil
  671. }
  672. func (tf *transformer) replaceAsmNames(buf *bytes.Buffer, remaining []byte) {
  673. // We need to replace all function references with their obfuscated name
  674. // counterparts.
  675. // Luckily, all func names in Go assembly files are immediately followed
  676. // by the unicode "middle dot", like:
  677. //
  678. // TEXT ·privateAdd(SB),$0-24
  679. // TEXT runtime∕internal∕sys·Ctz64(SB), NOSPLIT, $0-12
  680. //
  681. // Note that import paths in assembly, like `runtime∕internal∕sys` above,
  682. // use Unicode periods and slashes rather than the ASCII ones used by `go list`.
  683. // We need to convert to ASCII to find the right package information.
  684. const (
  685. asmPeriod = '·'
  686. goPeriod = '.'
  687. asmSlash = '∕'
  688. goSlash = '/'
  689. )
  690. asmPeriodLen := utf8.RuneLen(asmPeriod)
  691. for {
  692. periodIdx := bytes.IndexRune(remaining, asmPeriod)
  693. if periodIdx < 0 {
  694. buf.Write(remaining)
  695. remaining = nil
  696. break
  697. }
  698. // The package name ends at the first rune which cannot be part of a Go
  699. // import path, such as a comma or space.
  700. pkgStart := periodIdx
  701. for pkgStart >= 0 {
  702. c, size := utf8.DecodeLastRune(remaining[:pkgStart])
  703. if !unicode.IsLetter(c) && c != '_' && c != asmSlash && !unicode.IsDigit(c) {
  704. break
  705. }
  706. pkgStart -= size
  707. }
  708. // The package name might actually be longer, e.g:
  709. //
  710. // JMP test∕with·many·dots∕main∕imported·PublicAdd(SB)
  711. //
  712. // We have `test∕with` so far; grab `·many·dots∕main∕imported` as well.
  713. pkgEnd := periodIdx
  714. lastAsmPeriod := -1
  715. for i := pkgEnd + asmPeriodLen; i <= len(remaining); {
  716. c, size := utf8.DecodeRune(remaining[i:])
  717. if c == asmPeriod {
  718. lastAsmPeriod = i
  719. } else if !unicode.IsLetter(c) && c != '_' && c != asmSlash && !unicode.IsDigit(c) {
  720. if lastAsmPeriod > 0 {
  721. pkgEnd = lastAsmPeriod
  722. }
  723. break
  724. }
  725. i += size
  726. }
  727. asmPkgPath := string(remaining[pkgStart:pkgEnd])
  728. // Write the bytes before our unqualified `·foo` or qualified `pkg·foo`.
  729. buf.Write(remaining[:pkgStart])
  730. // If the name was qualified, fetch the package, and write the
  731. // obfuscated import path if needed.
  732. // Note that we don't obfuscate the package path "main".
  733. lpkg := tf.curPkg
  734. if asmPkgPath != "" && asmPkgPath != "main" {
  735. if asmPkgPath != tf.curPkg.Name {
  736. goPkgPath := asmPkgPath
  737. goPkgPath = strings.ReplaceAll(goPkgPath, string(asmPeriod), string(goPeriod))
  738. goPkgPath = strings.ReplaceAll(goPkgPath, string(asmSlash), string(goSlash))
  739. var err error
  740. lpkg, err = listPackage(tf.curPkg, goPkgPath)
  741. if err != nil {
  742. panic(err) // shouldn't happen
  743. }
  744. }
  745. if lpkg.ToObfuscate {
  746. // Note that we don't need to worry about asmSlash here,
  747. // because our obfuscated import paths contain no slashes right now.
  748. buf.WriteString(lpkg.obfuscatedImportPath())
  749. } else {
  750. buf.WriteString(asmPkgPath)
  751. }
  752. }
  753. // Write the middle dot and advance the remaining slice.
  754. buf.WriteRune(asmPeriod)
  755. remaining = remaining[pkgEnd+asmPeriodLen:]
  756. // The declared name ends at the first rune which cannot be part of a Go
  757. // identifier, such as a comma or space.
  758. nameEnd := 0
  759. for nameEnd < len(remaining) {
  760. c, size := utf8.DecodeRune(remaining[nameEnd:])
  761. if !unicode.IsLetter(c) && c != '_' && !unicode.IsDigit(c) {
  762. break
  763. }
  764. nameEnd += size
  765. }
  766. name := string(remaining[:nameEnd])
  767. remaining = remaining[nameEnd:]
  768. if lpkg.ToObfuscate && !compilerIntrinsicsFuncs[lpkg.ImportPath+"."+name] {
  769. newName := hashWithPackage(lpkg, name)
  770. if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed
  771. log.Printf("asm name %q hashed with %x to %q", name, tf.curPkg.GarbleActionID, newName)
  772. }
  773. buf.WriteString(newName)
  774. } else {
  775. buf.WriteString(name)
  776. }
  777. }
  778. }
  779. // writeSourceFile is a mix between os.CreateTemp and os.WriteFile, as it writes a
  780. // named source file in sharedTempDir given an input buffer.
  781. //
  782. // Note that the file is created under a directory tree following curPkg's
  783. // import path, mimicking how files are laid out in modules and GOROOT.
  784. func (tf *transformer) writeSourceFile(basename, obfuscated string, content []byte) (string, error) {
  785. // Uncomment for some quick debugging. Do not delete.
  786. // fmt.Fprintf(os.Stderr, "\n-- %s/%s --\n%s", curPkg.ImportPath, basename, content)
  787. if flagDebugDir != "" {
  788. pkgDir := filepath.Join(flagDebugDir, filepath.FromSlash(tf.curPkg.ImportPath))
  789. if err := os.MkdirAll(pkgDir, 0o755); err != nil {
  790. return "", err
  791. }
  792. dstPath := filepath.Join(pkgDir, basename)
  793. if err := os.WriteFile(dstPath, content, 0o666); err != nil {
  794. return "", err
  795. }
  796. }
  797. // We use the obfuscated import path to hold the temporary files.
  798. // Assembly files do not support line directives to set positions,
  799. // so the only way to not leak the import path is to replace it.
  800. pkgDir := filepath.Join(sharedTempDir, tf.curPkg.obfuscatedImportPath())
  801. if err := os.MkdirAll(pkgDir, 0o777); err != nil {
  802. return "", err
  803. }
  804. dstPath := filepath.Join(pkgDir, obfuscated)
  805. if err := writeFileExclusive(dstPath, content); err != nil {
  806. return "", err
  807. }
  808. return dstPath, nil
  809. }
  810. // parseFiles parses a list of Go files.
  811. // It supports relative file paths, such as those found in listedPackage.CompiledGoFiles,
  812. // as long as dir is set to listedPackage.Dir.
  813. func parseFiles(dir string, paths []string) ([]*ast.File, error) {
  814. var files []*ast.File
  815. for _, path := range paths {
  816. if !filepath.IsAbs(path) {
  817. path = filepath.Join(dir, path)
  818. }
  819. file, err := parser.ParseFile(fset, path, nil, parser.SkipObjectResolution|parser.ParseComments)
  820. if err != nil {
  821. return nil, err
  822. }
  823. files = append(files, file)
  824. }
  825. return files, nil
  826. }
  827. func (tf *transformer) transformCompile(args []string) ([]string, error) {
  828. flags, paths := splitFlagsFromFiles(args, ".go")
  829. // We will force the linker to drop DWARF via -w, so don't spend time
  830. // generating it.
  831. flags = append(flags, "-dwarf=false")
  832. // The Go file paths given to the compiler are always absolute paths.
  833. files, err := parseFiles("", paths)
  834. if err != nil {
  835. return nil, err
  836. }
  837. // Literal and control flow obfuscation uses math/rand, so seed it deterministically.
  838. randSeed := tf.curPkg.GarbleActionID[:]
  839. if flagSeed.present() {
  840. randSeed = flagSeed.bytes
  841. }
  842. // log.Printf("seeding math/rand with %x\n", randSeed)
  843. tf.obfRand = mathrand.New(mathrand.NewSource(int64(binary.BigEndian.Uint64(randSeed))))
  844. // Even if loadPkgCache below finds a direct cache hit,
  845. // other parts of garble still need type information to obfuscate.
  846. // We could potentially avoid this by saving the type info we need in the cache,
  847. // although in general that wouldn't help much, since it's rare for Go's cache
  848. // to miss on a package and for our cache to hit.
  849. if tf.pkg, tf.info, err = typecheck(tf.curPkg.ImportPath, files, tf.origImporter); err != nil {
  850. return nil, err
  851. }
  852. var (
  853. ssaPkg *ssa.Package
  854. requiredPkgs []string
  855. )
  856. if flagControlFlow {
  857. ssaPkg = ssaBuildPkg(tf.pkg, files, tf.info)
  858. newFileName, newFile, affectedFiles, err := ctrlflow.Obfuscate(fset, ssaPkg, files, tf.obfRand)
  859. if err != nil {
  860. return nil, err
  861. }
  862. if newFile != nil {
  863. files = append(files, newFile)
  864. paths = append(paths, newFileName)
  865. for _, file := range affectedFiles {
  866. tf.useAllImports(file)
  867. }
  868. if tf.pkg, tf.info, err = typecheck(tf.curPkg.ImportPath, files, tf.origImporter); err != nil {
  869. return nil, err
  870. }
  871. for _, imp := range newFile.Imports {
  872. path, err := strconv.Unquote(imp.Path.Value)
  873. if err != nil {
  874. panic(err) // should never happen
  875. }
  876. requiredPkgs = append(requiredPkgs, path)
  877. }
  878. }
  879. }
  880. if tf.curPkgCache, err = loadPkgCache(tf.curPkg, tf.pkg, files, tf.info, ssaPkg); err != nil {
  881. return nil, err
  882. }
  883. // These maps are not kept in pkgCache, since they are only needed to obfuscate curPkg.
  884. tf.fieldToStruct = computeFieldToStruct(tf.info)
  885. if flagLiterals {
  886. if tf.linkerVariableStrings, err = computeLinkerVariableStrings(tf.pkg); err != nil {
  887. return nil, err
  888. }
  889. }
  890. flags = alterTrimpath(flags)
  891. newImportCfg, err := tf.processImportCfg(flags, requiredPkgs)
  892. if err != nil {
  893. return nil, err
  894. }
  895. // If this is a package to obfuscate, swap the -p flag with the new package path.
  896. // We don't if it's the main package, as that just uses "-p main".
  897. // We only set newPkgPath if we're obfuscating the import path,
  898. // to replace the original package name in the package clause below.
  899. newPkgPath := ""
  900. if tf.curPkg.Name != "main" && tf.curPkg.ToObfuscate {
  901. newPkgPath = tf.curPkg.obfuscatedImportPath()
  902. flags = flagSetValue(flags, "-p", newPkgPath)
  903. }
  904. newPaths := make([]string, 0, len(files))
  905. for i, file := range files {
  906. basename := filepath.Base(paths[i])
  907. log.Printf("obfuscating %s", basename)
  908. if tf.curPkg.ImportPath == "runtime" {
  909. if flagTiny {
  910. // strip unneeded runtime code
  911. stripRuntime(basename, file)
  912. tf.useAllImports(file)
  913. }
  914. if basename == "symtab.go" {
  915. updateMagicValue(file, magicValue())
  916. updateEntryOffset(file, entryOffKey())
  917. }
  918. }
  919. tf.transformDirectives(file.Comments)
  920. file = tf.transformGoFile(file)
  921. // newPkgPath might be the original ImportPath in some edge cases like
  922. // compilerIntrinsics; we don't want to use slashes in package names.
  923. // TODO: when we do away with those edge cases, only check the string is
  924. // non-empty.
  925. if newPkgPath != "" && newPkgPath != tf.curPkg.ImportPath {
  926. file.Name.Name = newPkgPath
  927. }
  928. src, err := printFile(tf.curPkg, file)
  929. if err != nil {
  930. return nil, err
  931. }
  932. // We hide Go source filenames via "//line" directives,
  933. // so there is no need to use obfuscated filenames here.
  934. if path, err := tf.writeSourceFile(basename, basename, src); err != nil {
  935. return nil, err
  936. } else {
  937. newPaths = append(newPaths, path)
  938. }
  939. }
  940. flags = flagSetValue(flags, "-importcfg", newImportCfg)
  941. return append(flags, newPaths...), nil
  942. }
  943. // transformDirectives rewrites //go:linkname toolchain directives in comments
  944. // to replace names with their obfuscated versions.
  945. func (tf *transformer) transformDirectives(comments []*ast.CommentGroup) {
  946. for _, group := range comments {
  947. for _, comment := range group.List {
  948. if !strings.HasPrefix(comment.Text, "//go:linkname ") {
  949. continue
  950. }
  951. // We can have either just one argument:
  952. //
  953. // //go:linkname localName
  954. //
  955. // Or two arguments, where the second may refer to a name in a
  956. // different package:
  957. //
  958. // //go:linkname localName newName
  959. // //go:linkname localName pkg.newName
  960. fields := strings.Fields(comment.Text)
  961. localName := fields[1]
  962. newName := ""
  963. if len(fields) == 3 {
  964. newName = fields[2]
  965. }
  966. localName, newName = tf.transformLinkname(localName, newName)
  967. fields[1] = localName
  968. if len(fields) == 3 {
  969. fields[2] = newName
  970. }
  971. if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed
  972. log.Printf("linkname %q changed to %q", comment.Text, strings.Join(fields, " "))
  973. }
  974. comment.Text = strings.Join(fields, " ")
  975. }
  976. }
  977. }
  978. func (tf *transformer) transformLinkname(localName, newName string) (string, string) {
  979. // obfuscate the local name, if the current package is obfuscated
  980. if tf.curPkg.ToObfuscate && !compilerIntrinsicsFuncs[tf.curPkg.ImportPath+"."+localName] {
  981. localName = hashWithPackage(tf.curPkg, localName)
  982. }
  983. if newName == "" {
  984. return localName, ""
  985. }
  986. // If the new name is of the form "pkgpath.Name", and we've obfuscated
  987. // "Name" in that package, rewrite the directive to use the obfuscated name.
  988. dotCnt := strings.Count(newName, ".")
  989. if dotCnt < 1 {
  990. // cgo-generated code uses linknames to made up symbol names,
  991. // which do not have a package path at all.
  992. // Replace the comment in case the local name was obfuscated.
  993. return localName, newName
  994. }
  995. switch newName {
  996. case "main.main", "main..inittask", "runtime..inittask":
  997. // The runtime uses some special symbols with "..".
  998. // We aren't touching those at the moment.
  999. return localName, newName
  1000. }
  1001. pkgSplit := 0
  1002. var lpkg *listedPackage
  1003. var foreignName string
  1004. for {
  1005. i := strings.Index(newName[pkgSplit:], ".")
  1006. if i < 0 {
  1007. // We couldn't find a prefix that matched a known package.
  1008. // Probably a made up name like above, but with a dot.
  1009. return localName, newName
  1010. }
  1011. pkgSplit += i
  1012. pkgPath := newName[:pkgSplit]
  1013. pkgSplit++ // skip over the dot
  1014. if strings.HasSuffix(pkgPath, "_test") {
  1015. // runtime uses a go:linkname to metrics_test;
  1016. // we don't need this to work for now on regular builds,
  1017. // though we might need to rethink this if we want "go test std" to work.
  1018. continue
  1019. }
  1020. var err error
  1021. lpkg, err = listPackage(tf.curPkg, pkgPath)
  1022. if err == nil {
  1023. foreignName = newName[pkgSplit:]
  1024. break
  1025. }
  1026. if errors.Is(err, ErrNotFound) {
  1027. // No match; find the next dot.
  1028. continue
  1029. }
  1030. if errors.Is(err, ErrNotDependency) {
  1031. fmt.Fprintf(os.Stderr,
  1032. "//go:linkname refers to %s - add `import _ %q` for garble to find the package",
  1033. newName, pkgPath)
  1034. return localName, newName
  1035. }
  1036. panic(err) // shouldn't happen
  1037. }
  1038. if !lpkg.ToObfuscate || compilerIntrinsicsFuncs[lpkg.ImportPath+"."+foreignName] {
  1039. // We're not obfuscating that package or name.
  1040. return localName, newName
  1041. }
  1042. var newForeignName string
  1043. if receiver, name, ok := strings.Cut(foreignName, "."); ok {
  1044. if lpkg.ImportPath == "reflect" && (receiver == "(*rtype)" || receiver == "Value") {
  1045. // These receivers are not obfuscated.
  1046. // See the TODO below.
  1047. } else if strings.HasPrefix(receiver, "(*") {
  1048. // pkg/path.(*Receiver).method
  1049. receiver = strings.TrimPrefix(receiver, "(*")
  1050. receiver = strings.TrimSuffix(receiver, ")")
  1051. receiver = "(*" + hashWithPackage(lpkg, receiver) + ")"
  1052. } else {
  1053. // pkg/path.Receiver.method
  1054. receiver = hashWithPackage(lpkg, receiver)
  1055. }
  1056. // Exported methods are never obfuscated.
  1057. //
  1058. // TODO(mvdan): We're duplicating the logic behind these decisions.
  1059. // Reuse the logic with transformCompile.
  1060. if !token.IsExported(name) {
  1061. name = hashWithPackage(lpkg, name)
  1062. }
  1063. newForeignName = receiver + "." + name
  1064. } else {
  1065. // pkg/path.function
  1066. newForeignName = hashWithPackage(lpkg, foreignName)
  1067. }
  1068. newPkgPath := lpkg.ImportPath
  1069. if newPkgPath != "main" {
  1070. newPkgPath = lpkg.obfuscatedImportPath()
  1071. }
  1072. newName = newPkgPath + "." + newForeignName
  1073. return localName, newName
  1074. }
  1075. // processImportCfg parses the importcfg file passed to a compile or link step.
  1076. // It also builds a new importcfg file to account for obfuscated import paths.
  1077. func (tf *transformer) processImportCfg(flags []string, requiredPkgs []string) (newImportCfg string, _ error) {
  1078. importCfg := flagValue(flags, "-importcfg")
  1079. if importCfg == "" {
  1080. return "", fmt.Errorf("could not find -importcfg argument")
  1081. }
  1082. data, err := os.ReadFile(importCfg)
  1083. if err != nil {
  1084. return "", err
  1085. }
  1086. var packagefiles, importmaps [][2]string
  1087. // using for track required but not imported packages
  1088. var newIndirectImports map[string]bool
  1089. if requiredPkgs != nil {
  1090. newIndirectImports = make(map[string]bool)
  1091. for _, pkg := range requiredPkgs {
  1092. newIndirectImports[pkg] = true
  1093. }
  1094. }
  1095. for _, line := range strings.Split(string(data), "\n") {
  1096. if line == "" || strings.HasPrefix(line, "#") {
  1097. continue
  1098. }
  1099. verb, args, found := strings.Cut(line, " ")
  1100. if !found {
  1101. continue
  1102. }
  1103. switch verb {
  1104. case "importmap":
  1105. beforePath, afterPath, found := strings.Cut(args, "=")
  1106. if !found {
  1107. continue
  1108. }
  1109. importmaps = append(importmaps, [2]string{beforePath, afterPath})
  1110. case "packagefile":
  1111. importPath, objectPath, found := strings.Cut(args, "=")
  1112. if !found {
  1113. continue
  1114. }
  1115. packagefiles = append(packagefiles, [2]string{importPath, objectPath})
  1116. delete(newIndirectImports, importPath)
  1117. }
  1118. }
  1119. // Produce the modified importcfg file.
  1120. // This is mainly replacing the obfuscated paths.
  1121. // Note that we range over maps, so this is non-deterministic, but that
  1122. // should not matter as the file is treated like a lookup table.
  1123. newCfg, err := os.CreateTemp(sharedTempDir, "importcfg")
  1124. if err != nil {
  1125. return "", err
  1126. }
  1127. for _, pair := range importmaps {
  1128. beforePath, afterPath := pair[0], pair[1]
  1129. lpkg, err := listPackage(tf.curPkg, beforePath)
  1130. if err != nil {
  1131. panic(err) // shouldn't happen
  1132. }
  1133. if lpkg.ToObfuscate {
  1134. // Note that beforePath is not the canonical path.
  1135. // For beforePath="vendor/foo", afterPath and
  1136. // lpkg.ImportPath can be just "foo".
  1137. // Don't use obfuscatedImportPath here.
  1138. beforePath = hashWithPackage(lpkg, beforePath)
  1139. afterPath = lpkg.obfuscatedImportPath()
  1140. }
  1141. fmt.Fprintf(newCfg, "importmap %s=%s\n", beforePath, afterPath)
  1142. }
  1143. if len(newIndirectImports) > 0 {
  1144. f, err := os.Open(filepath.Join(sharedTempDir, actionGraphFileName))
  1145. if err != nil {
  1146. return "", fmt.Errorf("cannot open action graph file: %v", err)
  1147. }
  1148. defer f.Close()
  1149. var actions []struct {
  1150. Mode string
  1151. Package string
  1152. Objdir string
  1153. }
  1154. if err := json.NewDecoder(f).Decode(&actions); err != nil {
  1155. return "", fmt.Errorf("cannot parse action graph file: %v", err)
  1156. }
  1157. // theoretically action graph can be long, to optimise it process it in one pass
  1158. // with an early exit when all the required imports are found
  1159. for _, action := range actions {
  1160. if action.Mode != "build" {
  1161. continue
  1162. }
  1163. if ok := newIndirectImports[action.Package]; !ok {
  1164. continue
  1165. }
  1166. packagefiles = append(packagefiles, [2]string{action.Package, filepath.Join(action.Objdir, "_pkg_.a")}) // file name hardcoded in compiler
  1167. delete(newIndirectImports, action.Package)
  1168. if len(newIndirectImports) == 0 {
  1169. break
  1170. }
  1171. }
  1172. if len(newIndirectImports) > 0 {
  1173. return "", fmt.Errorf("cannot resolve required packages from action graph file: %v", requiredPkgs)
  1174. }
  1175. }
  1176. for _, pair := range packagefiles {
  1177. impPath, pkgfile := pair[0], pair[1]
  1178. lpkg, err := listPackage(tf.curPkg, impPath)
  1179. if err != nil {
  1180. // TODO: it's unclear why an importcfg can include an import path
  1181. // that's not a dependency in an edge case with "go test ./...".
  1182. // See exporttest/*.go in testdata/scripts/test.txt.
  1183. // For now, spot the pattern and avoid the unnecessary error;
  1184. // the dependency is unused, so the packagefile line is redundant.
  1185. // This still triggers as of go1.20.
  1186. if strings.HasSuffix(tf.curPkg.ImportPath, ".test]") && strings.HasPrefix(tf.curPkg.ImportPath, impPath) {
  1187. continue
  1188. }
  1189. panic(err) // shouldn't happen
  1190. }
  1191. if lpkg.Name != "main" {
  1192. impPath = lpkg.obfuscatedImportPath()
  1193. }
  1194. fmt.Fprintf(newCfg, "packagefile %s=%s\n", impPath, pkgfile)
  1195. }
  1196. // Uncomment to debug the transformed importcfg. Do not delete.
  1197. // newCfg.Seek(0, 0)
  1198. // io.Copy(os.Stderr, newCfg)
  1199. if err := newCfg.Close(); err != nil {
  1200. return "", err
  1201. }
  1202. return newCfg.Name(), nil
  1203. }
  1204. type (
  1205. funcFullName = string // as per go/types.Func.FullName
  1206. objectString = string // as per recordedObjectString
  1207. typeName struct {
  1208. PkgPath, Name string
  1209. }
  1210. )
  1211. // pkgCache contains information about a package that will be stored in fsCache.
  1212. // Note that pkgCache is "deep", containing information about all packages
  1213. // which are transitive dependencies as well.
  1214. type pkgCache struct {
  1215. // ReflectAPIs is a static record of what std APIs use reflection on their
  1216. // parameters, so we can avoid obfuscating types used with them.
  1217. //
  1218. // TODO: we're not including fmt.Printf, as it would have many false positives,
  1219. // unless we were smart enough to detect which arguments get used as %#v or %T.
  1220. ReflectAPIs map[funcFullName]map[int]bool
  1221. // ReflectObjects is filled with the fully qualified names from each
  1222. // package that we cannot obfuscate due to reflection.
  1223. // The included objects are named types and their fields,
  1224. // since it is those names being obfuscated that could break the use of reflect.
  1225. //
  1226. // This record is necessary for knowing what names from imported packages
  1227. // weren't obfuscated, so we can obfuscate their local uses accordingly.
  1228. ReflectObjects map[objectString]struct{}
  1229. // EmbeddedAliasFields records which embedded fields use a type alias.
  1230. // They are the only instance where a type alias matters for obfuscation,
  1231. // because the embedded field name is derived from the type alias itself,
  1232. // and not the type that the alias points to.
  1233. // In that way, the type alias is obfuscated as a form of named type,
  1234. // bearing in mind that it may be owned by a different package.
  1235. EmbeddedAliasFields map[objectString]typeName
  1236. }
  1237. func (c *pkgCache) CopyFrom(c2 pkgCache) {
  1238. maps.Copy(c.ReflectAPIs, c2.ReflectAPIs)
  1239. maps.Copy(c.ReflectObjects, c2.ReflectObjects)
  1240. maps.Copy(c.EmbeddedAliasFields, c2.EmbeddedAliasFields)
  1241. }
  1242. func ssaBuildPkg(pkg *types.Package, files []*ast.File, info *types.Info) *ssa.Package {
  1243. // Create SSA packages for all imports. Order is not significant.
  1244. ssaProg := ssa.NewProgram(fset, 0)
  1245. created := make(map[*types.Package]bool)
  1246. var createAll func(pkgs []*types.Package)
  1247. createAll = func(pkgs []*types.Package) {
  1248. for _, p := range pkgs {
  1249. if !created[p] {
  1250. created[p] = true
  1251. ssaProg.CreatePackage(p, nil, nil, true)
  1252. createAll(p.Imports())
  1253. }
  1254. }
  1255. }
  1256. createAll(pkg.Imports())
  1257. ssaPkg := ssaProg.CreatePackage(pkg, files, info, false)
  1258. ssaPkg.Build()
  1259. return ssaPkg
  1260. }
  1261. func openCache() (*cache.Cache, error) {
  1262. // Use a subdirectory for the hashed build cache, to clarify what it is,
  1263. // and to allow us to have other directories or files later on without mixing.
  1264. dir := filepath.Join(sharedCache.CacheDir, "build")
  1265. if err := os.MkdirAll(dir, 0o777); err != nil {
  1266. return nil, err
  1267. }
  1268. return cache.Open(dir)
  1269. }
  1270. func loadPkgCache(lpkg *listedPackage, pkg *types.Package, files []*ast.File, info *types.Info, ssaPkg *ssa.Package) (pkgCache, error) {
  1271. fsCache, err := openCache()
  1272. if err != nil {
  1273. return pkgCache{}, err
  1274. }
  1275. filename, _, err := fsCache.GetFile(lpkg.GarbleActionID)
  1276. // Already in the cache; load it directly.
  1277. if err == nil {
  1278. f, err := os.Open(filename)
  1279. if err != nil {
  1280. return pkgCache{}, err
  1281. }
  1282. defer f.Close()
  1283. var loaded pkgCache
  1284. if err := gob.NewDecoder(f).Decode(&loaded); err != nil {
  1285. return pkgCache{}, fmt.Errorf("gob decode: %w", err)
  1286. }
  1287. return loaded, nil
  1288. }
  1289. return computePkgCache(fsCache, lpkg, pkg, files, info, ssaPkg)
  1290. }
  1291. func computePkgCache(fsCache *cache.Cache, lpkg *listedPackage, pkg *types.Package, files []*ast.File, info *types.Info, ssaPkg *ssa.Package) (pkgCache, error) {
  1292. // Not yet in the cache. Load the cache entries for all direct dependencies,
  1293. // build our cache entry, and write it to disk.
  1294. // Note that practically all errors from Cache.GetFile are a cache miss;
  1295. // for example, a file might exist but be empty if another process
  1296. // is filling the same cache entry concurrently.
  1297. //
  1298. // TODO: if A (curPkg) imports B and C, and B also imports C,
  1299. // then loading the gob files from both B and C is unnecessary;
  1300. // loading B's gob file would be enough. Is there an easy way to do that?
  1301. computed := pkgCache{
  1302. ReflectAPIs: map[funcFullName]map[int]bool{
  1303. "reflect.TypeOf": {0: true},
  1304. "reflect.ValueOf": {0: true},
  1305. },
  1306. ReflectObjects: map[objectString]struct{}{},
  1307. EmbeddedAliasFields: map[objectString]typeName{},
  1308. }
  1309. for _, imp := range lpkg.Imports {
  1310. if imp == "C" {
  1311. // `go list -json` shows "C" in Imports but not Deps.
  1312. // See https://go.dev/issue/60453.
  1313. continue
  1314. }
  1315. // Shadowing lpkg ensures we don't use the wrong listedPackage below.
  1316. lpkg, err := listPackage(lpkg, imp)
  1317. if err != nil {
  1318. panic(err) // shouldn't happen
  1319. }
  1320. if lpkg.BuildID == "" {
  1321. continue // nothing to load
  1322. }
  1323. if err := func() error { // function literal for the deferred close
  1324. if filename, _, err := fsCache.GetFile(lpkg.GarbleActionID); err == nil {
  1325. // Cache hit; append new entries to computed.
  1326. f, err := os.Open(filename)
  1327. if err != nil {
  1328. return err
  1329. }
  1330. defer f.Close()
  1331. if err := gob.NewDecoder(f).Decode(&computed); err != nil {
  1332. return fmt.Errorf("gob decode: %w", err)
  1333. }
  1334. return nil
  1335. }
  1336. // Missing or corrupted entry in the cache for a dependency.
  1337. // Could happen if GARBLE_CACHE was emptied but GOCACHE was not.
  1338. // Compute it, which can recurse if many entries are missing.
  1339. files, err := parseFiles(lpkg.Dir, lpkg.CompiledGoFiles)
  1340. if err != nil {
  1341. return err
  1342. }
  1343. origImporter := importerForPkg(lpkg)
  1344. pkg, info, err := typecheck(lpkg.ImportPath, files, origImporter)
  1345. if err != nil {
  1346. return err
  1347. }
  1348. computedImp, err := computePkgCache(fsCache, lpkg, pkg, files, info, nil)
  1349. if err != nil {
  1350. return err
  1351. }
  1352. computed.CopyFrom(computedImp)
  1353. return nil
  1354. }(); err != nil {
  1355. return pkgCache{}, fmt.Errorf("pkgCache load for %s: %w", imp, err)
  1356. }
  1357. }
  1358. // Fill EmbeddedAliasFields from the type info.
  1359. for name, obj := range info.Uses {
  1360. obj, ok := obj.(*types.TypeName)
  1361. if !ok || !obj.IsAlias() {
  1362. continue
  1363. }
  1364. vr, _ := info.Defs[name].(*types.Var)
  1365. if vr == nil || !vr.Embedded() {
  1366. continue
  1367. }
  1368. vrStr := recordedObjectString(vr)
  1369. if vrStr == "" {
  1370. continue
  1371. }
  1372. aliasTypeName := typeName{
  1373. PkgPath: obj.Pkg().Path(),
  1374. Name: obj.Name(),
  1375. }
  1376. computed.EmbeddedAliasFields[vrStr] = aliasTypeName
  1377. }
  1378. // Fill the reflect info from SSA, which builds on top of the syntax tree and type info.
  1379. inspector := reflectInspector{
  1380. pkg: pkg,
  1381. checkedAPIs: make(map[string]bool),
  1382. propagatedStores: map[*ssa.Store]bool{},
  1383. result: computed, // append the results
  1384. }
  1385. if ssaPkg == nil {
  1386. ssaPkg = ssaBuildPkg(pkg, files, info)
  1387. }
  1388. inspector.recordReflection(ssaPkg)
  1389. // Unlikely that we could stream the gob encode, as cache.Put wants an io.ReadSeeker.
  1390. var buf bytes.Buffer
  1391. if err := gob.NewEncoder(&buf).Encode(computed); err != nil {
  1392. return pkgCache{}, err
  1393. }
  1394. if err := fsCache.PutBytes(lpkg.GarbleActionID, buf.Bytes()); err != nil {
  1395. return pkgCache{}, err
  1396. }
  1397. return computed, nil
  1398. }
  1399. // cmd/bundle will include a go:generate directive in its output by default.
  1400. // Ours specifies a version and doesn't assume bundle is in $PATH, so drop it.
  1401. //go:generate go run golang.org/x/tools/cmd/bundle@v0.5.0 -o cmdgo_quoted.go -prefix cmdgoQuoted cmd/internal/quoted
  1402. //go:generate sed -i /go:generate/d cmdgo_quoted.go
  1403. // computeLinkerVariableStrings iterates over the -ldflags arguments,
  1404. // filling a map with all the string values set via the linker's -X flag.
  1405. // TODO: can we put this in sharedCache, using objectString as a key?
  1406. func computeLinkerVariableStrings(pkg *types.Package) (map[*types.Var]string, error) {
  1407. linkerVariableStrings := make(map[*types.Var]string)
  1408. // TODO: this is a linker flag that affects how we obfuscate a package at
  1409. // compile time. Note that, if the user changes ldflags, then Go may only
  1410. // re-link the final binary, without re-compiling any packages at all.
  1411. // It's possible that this could result in:
  1412. //
  1413. // garble -literals build -ldflags=-X=pkg.name=before # name="before"
  1414. // garble -literals build -ldflags=-X=pkg.name=after # name="before" as cached
  1415. //
  1416. // We haven't been able to reproduce this problem for now,
  1417. // but it's worth noting it and keeping an eye out for it in the future.
  1418. // If we do confirm this theoretical bug,
  1419. // the solution will be to either find a different solution for -literals,
  1420. // or to force including -ldflags into the build cache key.
  1421. ldflags, err := cmdgoQuotedSplit(flagValue(sharedCache.ForwardBuildFlags, "-ldflags"))
  1422. if err != nil {
  1423. return nil, err
  1424. }
  1425. flagValueIter(ldflags, "-X", func(val string) {
  1426. // val is in the form of "foo.com/bar.name=value".
  1427. fullName, stringValue, found := strings.Cut(val, "=")
  1428. if !found {
  1429. return // invalid
  1430. }
  1431. // fullName is "foo.com/bar.name"
  1432. i := strings.LastIndexByte(fullName, '.')
  1433. path, name := fullName[:i], fullName[i+1:]
  1434. // -X represents the main package as "main", not its import path.
  1435. if path != pkg.Path() && (path != "main" || pkg.Name() != "main") {
  1436. return // not the current package
  1437. }
  1438. obj, _ := pkg.Scope().Lookup(name).(*types.Var)
  1439. if obj == nil {
  1440. return // no such variable; skip
  1441. }
  1442. linkerVariableStrings[obj] = stringValue
  1443. })
  1444. return linkerVariableStrings, nil
  1445. }
  1446. // transformer holds all the information and state necessary to obfuscate a
  1447. // single Go package.
  1448. type transformer struct {
  1449. // curPkg holds basic information about the package being currently compiled or linked.
  1450. curPkg *listedPackage
  1451. // curPkgCache is the pkgCache for curPkg.
  1452. curPkgCache pkgCache
  1453. // The type-checking results; the package itself, and the Info struct.
  1454. pkg *types.Package
  1455. info *types.Info
  1456. // linkerVariableStrings records objects for variables used in -ldflags=-X flags,
  1457. // as well as the strings the user wants to inject them with.
  1458. // Used when obfuscating literals, so that we obfuscate the injected value.
  1459. linkerVariableStrings map[*types.Var]string
  1460. // fieldToStruct helps locate struct types from any of their field
  1461. // objects. Useful when obfuscating field names.
  1462. fieldToStruct map[*types.Var]*types.Struct
  1463. // obfRand is initialized by transformCompile and used during obfuscation.
  1464. // It is left nil at init time, so that we only use it after it has been
  1465. // properly initialized with a deterministic seed.
  1466. // It must only be used for deterministic obfuscation;
  1467. // if it is used for any other purpose, we may lose determinism.
  1468. obfRand *mathrand.Rand
  1469. // origImporter is a go/types importer which uses the original versions
  1470. // of packages, without any obfuscation. This is helpful to make
  1471. // decisions on how to obfuscate our input code.
  1472. origImporter importerWithMap
  1473. // usedAllImportsFiles is used to prevent multiple calls of tf.useAllImports function on one file
  1474. // in case of simultaneously applied control flow and literals obfuscation
  1475. usedAllImportsFiles map[*ast.File]bool
  1476. }
  1477. func typecheck(pkgPath string, files []*ast.File, origImporter importerWithMap) (*types.Package, *types.Info, error) {
  1478. info := &types.Info{
  1479. Types: make(map[ast.Expr]types.TypeAndValue),
  1480. Defs: make(map[*ast.Ident]types.Object),
  1481. Uses: make(map[*ast.Ident]types.Object),
  1482. Implicits: make(map[ast.Node]types.Object),
  1483. Scopes: make(map[ast.Node]*types.Scope),
  1484. Selections: make(map[*ast.SelectorExpr]*types.Selection),
  1485. Instances: make(map[*ast.Ident]types.Instance),
  1486. }
  1487. origTypesConfig := types.Config{Importer: origImporter}
  1488. pkg, err := origTypesConfig.Check(pkgPath, fset, files, info)
  1489. if err != nil {
  1490. return nil, nil, fmt.Errorf("typecheck error: %v", err)
  1491. }
  1492. return pkg, info, err
  1493. }
  1494. func computeFieldToStruct(info *types.Info) map[*types.Var]*types.Struct {
  1495. done := make(map[*types.Named]bool)
  1496. fieldToStruct := make(map[*types.Var]*types.Struct)
  1497. // Run recordType on all types reachable via types.Info.
  1498. // A bit hacky, but I could not find an easier way to do this.
  1499. for _, obj := range info.Uses {
  1500. if obj != nil {
  1501. recordType(obj.Type(), nil, done, fieldToStruct)
  1502. }
  1503. }
  1504. for _, obj := range info.Defs {
  1505. if obj != nil {
  1506. recordType(obj.Type(), nil, done, fieldToStruct)
  1507. }
  1508. }
  1509. for _, tv := range info.Types {
  1510. recordType(tv.Type, nil, done, fieldToStruct)
  1511. }
  1512. return fieldToStruct
  1513. }
  1514. // recordType visits every reachable type after typechecking a package.
  1515. // Right now, all it does is fill the fieldToStruct map.
  1516. // Since types can be recursive, we need a map to avoid cycles.
  1517. // We only need to track named types as done, as all cycles must use them.
  1518. func recordType(used, origin types.Type, done map[*types.Named]bool, fieldToStruct map[*types.Var]*types.Struct) {
  1519. if origin == nil {
  1520. origin = used
  1521. }
  1522. type Container interface{ Elem() types.Type }
  1523. switch used := used.(type) {
  1524. case Container:
  1525. // origin may be a *types.TypeParam, which is not a Container.
  1526. // For now, we haven't found a need to recurse in that case.
  1527. // We can edit this code in the future if we find an example,
  1528. // because we panic if a field is not in fieldToStruct.
  1529. if origin, ok := origin.(Container); ok {
  1530. recordType(used.Elem(), origin.Elem(), done, fieldToStruct)
  1531. }
  1532. case *types.Named:
  1533. if done[used] {
  1534. return
  1535. }
  1536. done[used] = true
  1537. // If we have a generic struct like
  1538. //
  1539. // type Foo[T any] struct { Bar T }
  1540. //
  1541. // then we want the hashing to use the original "Bar T",
  1542. // because otherwise different instances like "Bar int" and "Bar bool"
  1543. // will result in different hashes and the field names will break.
  1544. // Ensure we record the original generic struct, if there is one.
  1545. recordType(used.Underlying(), used.Origin().Underlying(), done, fieldToStruct)
  1546. case *types.Struct:
  1547. origin := origin.(*types.Struct)
  1548. for i := 0; i < used.NumFields(); i++ {
  1549. field := used.Field(i)
  1550. fieldToStruct[field] = origin
  1551. if field.Embedded() {
  1552. recordType(field.Type(), origin.Field(i).Type(), done, fieldToStruct)
  1553. }
  1554. }
  1555. }
  1556. }
  1557. // isSafeForInstanceType returns true if the passed type is safe for var declaration.
  1558. // Unsafe types: generic types and non-method interfaces.
  1559. func isSafeForInstanceType(typ types.Type) bool {
  1560. switch t := typ.(type) {
  1561. case *types.Named:
  1562. if t.TypeParams().Len() > 0 {
  1563. return false
  1564. }
  1565. return isSafeForInstanceType(t.Underlying())
  1566. case *types.Signature:
  1567. return t.TypeParams().Len() == 0
  1568. case *types.Interface:
  1569. return t.IsMethodSet()
  1570. }
  1571. return true
  1572. }
  1573. func (tf *transformer) useAllImports(file *ast.File) {
  1574. if tf.usedAllImportsFiles == nil {
  1575. tf.usedAllImportsFiles = make(map[*ast.File]bool)
  1576. } else if ok := tf.usedAllImportsFiles[file]; ok {
  1577. return
  1578. }
  1579. tf.usedAllImportsFiles[file] = true
  1580. for _, imp := range file.Imports {
  1581. if imp.Name != nil && imp.Name.Name == "_" {
  1582. continue
  1583. }
  1584. // Simple import has no ast.Ident and is stored in Implicits separately.
  1585. pkgObj := tf.info.Implicits[imp]
  1586. if pkgObj == nil {
  1587. pkgObj = tf.info.Defs[imp.Name] // renamed or dot import
  1588. }
  1589. pkgScope := pkgObj.(*types.PkgName).Imported().Scope()
  1590. var nameObj types.Object
  1591. for _, name := range pkgScope.Names() {
  1592. if obj := pkgScope.Lookup(name); obj.Exported() && isSafeForInstanceType(obj.Type()) {
  1593. nameObj = obj
  1594. break
  1595. }
  1596. }
  1597. if nameObj == nil {
  1598. // A very unlikely situation where there is no suitable declaration for a reference variable
  1599. // and almost certainly means that there is another import reference in code.
  1600. continue
  1601. }
  1602. spec := &ast.ValueSpec{Names: []*ast.Ident{ast.NewIdent("_")}}
  1603. decl := &ast.GenDecl{Specs: []ast.Spec{spec}}
  1604. nameIdent := ast.NewIdent(nameObj.Name())
  1605. var nameExpr ast.Expr
  1606. switch {
  1607. case imp.Name == nil: // import "pkg/path"
  1608. nameExpr = &ast.SelectorExpr{
  1609. X: ast.NewIdent(pkgObj.Name()),
  1610. Sel: nameIdent,
  1611. }
  1612. case imp.Name.Name != ".": // import path2 "pkg/path"
  1613. nameExpr = &ast.SelectorExpr{
  1614. X: ast.NewIdent(imp.Name.Name),
  1615. Sel: nameIdent,
  1616. }
  1617. default: // import . "pkg/path"
  1618. nameExpr = nameIdent
  1619. }
  1620. switch nameObj.(type) {
  1621. case *types.Const:
  1622. // const _ = <value>
  1623. decl.Tok = token.CONST
  1624. spec.Values = []ast.Expr{nameExpr}
  1625. case *types.Var, *types.Func:
  1626. // var _ = <value>
  1627. decl.Tok = token.VAR
  1628. spec.Values = []ast.Expr{nameExpr}
  1629. case *types.TypeName:
  1630. // var _ <type>
  1631. decl.Tok = token.VAR
  1632. spec.Type = nameExpr
  1633. default:
  1634. continue // skip *types.Builtin and others
  1635. }
  1636. // Ensure that types.Info.Uses is up to date.
  1637. tf.info.Uses[nameIdent] = nameObj
  1638. file.Decls = append(file.Decls, decl)
  1639. }
  1640. }
  1641. // transformGoFile obfuscates the provided Go syntax file.
  1642. func (tf *transformer) transformGoFile(file *ast.File) *ast.File {
  1643. // Only obfuscate the literals here if the flag is on
  1644. // and if the package in question is to be obfuscated.
  1645. //
  1646. // We can't obfuscate literals in the runtime and its dependencies,
  1647. // because obfuscated literals sometimes escape to heap,
  1648. // and that's not allowed in the runtime itself.
  1649. if flagLiterals && tf.curPkg.ToObfuscate {
  1650. file = literals.Obfuscate(tf.obfRand, file, tf.info, tf.linkerVariableStrings)
  1651. // some imported constants might not be needed anymore, remove unnecessary imports
  1652. tf.useAllImports(file)
  1653. }
  1654. pre := func(cursor *astutil.Cursor) bool {
  1655. node, ok := cursor.Node().(*ast.Ident)
  1656. if !ok {
  1657. return true
  1658. }
  1659. name := node.Name
  1660. if name == "_" {
  1661. return true // unnamed remains unnamed
  1662. }
  1663. obj := tf.info.ObjectOf(node)
  1664. if obj == nil {
  1665. _, isImplicit := tf.info.Defs[node]
  1666. _, parentIsFile := cursor.Parent().(*ast.File)
  1667. if !isImplicit || parentIsFile {
  1668. // We only care about nil objects in the switch scenario below.
  1669. return true
  1670. }
  1671. // In a type switch like "switch foo := bar.(type) {",
  1672. // "foo" is being declared as a symbolic variable,
  1673. // as it is only actually declared in each "case SomeType:".
  1674. //
  1675. // As such, the symbolic "foo" in the syntax tree has no object,
  1676. // but it is still recorded under Defs with a nil value.
  1677. // We still want to obfuscate that syntax tree identifier,
  1678. // so if we detect the case, create a dummy types.Var for it.
  1679. //
  1680. // Note that "package mypkg" also denotes a nil object in Defs,
  1681. // and we don't want to treat that "mypkg" as a variable,
  1682. // so avoid that case by checking the type of cursor.Parent.
  1683. obj = types.NewVar(node.Pos(), tf.pkg, name, nil)
  1684. }
  1685. pkg := obj.Pkg()
  1686. if vr, ok := obj.(*types.Var); ok && vr.Embedded() {
  1687. // The docs for ObjectOf say:
  1688. //
  1689. // If id is an embedded struct field, ObjectOf returns the
  1690. // field (*Var) it defines, not the type (*TypeName) it uses.
  1691. //
  1692. // If this embedded field is a type alias, we want to
  1693. // handle the alias's TypeName instead of treating it as
  1694. // the type the alias points to.
  1695. //
  1696. // Alternatively, if we don't have an alias, we still want to
  1697. // use the embedded type, not the field.
  1698. vrStr := recordedObjectString(vr)
  1699. aliasTypeName, ok := tf.curPkgCache.EmbeddedAliasFields[vrStr]
  1700. if ok {
  1701. pkg2 := tf.pkg
  1702. if path := aliasTypeName.PkgPath; pkg2.Path() != path {
  1703. // If the package is a dependency, import it.
  1704. // We can't grab the package via tf.pkg.Imports,
  1705. // because some of the packages under there are incomplete.
  1706. // ImportFrom will cache complete imports, anyway.
  1707. var err error
  1708. pkg2, err = tf.origImporter.ImportFrom(path, parentWorkDir, 0)
  1709. if err != nil {
  1710. panic(err)
  1711. }
  1712. }
  1713. tname, ok := pkg2.Scope().Lookup(aliasTypeName.Name).(*types.TypeName)
  1714. if !ok {
  1715. panic(fmt.Sprintf("EmbeddedAliasFields pointed %q to a missing type %q", vrStr, aliasTypeName))
  1716. }
  1717. if !tname.IsAlias() {
  1718. panic(fmt.Sprintf("EmbeddedAliasFields pointed %q to a non-alias type %q", vrStr, aliasTypeName))
  1719. }
  1720. obj = tname
  1721. } else {
  1722. named := namedType(obj.Type())
  1723. if named == nil {
  1724. return true // unnamed type (probably a basic type, e.g. int)
  1725. }
  1726. obj = named.Obj()
  1727. }
  1728. pkg = obj.Pkg()
  1729. }
  1730. if pkg == nil {
  1731. return true // universe scope
  1732. }
  1733. // TODO: We match by object name here, which is actually imprecise.
  1734. // For example, in package embed we match the type FS, but we would also
  1735. // match any field or method named FS.
  1736. // Can we instead use an object map like ReflectObjects?
  1737. path := pkg.Path()
  1738. switch path {
  1739. case "sync/atomic", "runtime/internal/atomic":
  1740. if name == "align64" {
  1741. return true
  1742. }
  1743. case "embed":
  1744. // FS is detected by the compiler for //go:embed.
  1745. if name == "FS" {
  1746. return true
  1747. }
  1748. case "reflect":
  1749. switch name {
  1750. // Per the linker's deadcode.go docs,
  1751. // the Method and MethodByName methods are what drive the logic.
  1752. case "Method", "MethodByName":
  1753. return true
  1754. }
  1755. case "crypto/x509/pkix":
  1756. // For better or worse, encoding/asn1 detects a "SET" suffix on slice type names
  1757. // to tell whether those slices should be treated as sets or sequences.
  1758. // Do not obfuscate those names to prevent breaking x509 certificates.
  1759. // TODO: we can surely do better; ideally propose a non-string-based solution
  1760. // upstream, or as a fallback, obfuscate to a name ending with "SET".
  1761. if strings.HasSuffix(name, "SET") {
  1762. return true
  1763. }
  1764. }
  1765. // The package that declared this object did not obfuscate it.
  1766. if usedForReflect(tf.curPkgCache, obj) {
  1767. return true
  1768. }
  1769. lpkg, err := listPackage(tf.curPkg, path)
  1770. if err != nil {
  1771. panic(err) // shouldn't happen
  1772. }
  1773. if !lpkg.ToObfuscate {
  1774. return true // we're not obfuscating this package
  1775. }
  1776. hashToUse := lpkg.GarbleActionID
  1777. debugName := "variable"
  1778. // log.Printf("%s: %#v %T", fset.Position(node.Pos()), node, obj)
  1779. switch obj := obj.(type) {
  1780. case *types.Var:
  1781. if !obj.IsField() {
  1782. // Identifiers denoting variables are always obfuscated.
  1783. break
  1784. }
  1785. debugName = "field"
  1786. // From this point on, we deal with struct fields.
  1787. // Fields don't get hashed with the package's action ID.
  1788. // They get hashed with the type of their parent struct.
  1789. // This is because one struct can be converted to another,
  1790. // as long as the underlying types are identical,
  1791. // even if the structs are defined in different packages.
  1792. //
  1793. // TODO: Consider only doing this for structs where all
  1794. // fields are exported. We only need this special case
  1795. // for cross-package conversions, which can't work if
  1796. // any field is unexported. If that is done, add a test
  1797. // that ensures unexported fields from different
  1798. // packages result in different obfuscated names.
  1799. strct := tf.fieldToStruct[obj]
  1800. if strct == nil {
  1801. panic("could not find struct for field " + name)
  1802. }
  1803. node.Name = hashWithStruct(strct, name)
  1804. if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed
  1805. log.Printf("%s %q hashed with struct fields to %q", debugName, name, node.Name)
  1806. }
  1807. return true
  1808. case *types.TypeName:
  1809. debugName = "type"
  1810. case *types.Func:
  1811. if compilerIntrinsicsFuncs[path+"."+name] {
  1812. return true
  1813. }
  1814. sign := obj.Type().(*types.Signature)
  1815. if sign.Recv() == nil {
  1816. debugName = "func"
  1817. } else {
  1818. debugName = "method"
  1819. }
  1820. if obj.Exported() && sign.Recv() != nil {
  1821. return true // might implement an interface
  1822. }
  1823. switch name {
  1824. case "main", "init", "TestMain":
  1825. return true // don't break them
  1826. }
  1827. if strings.HasPrefix(name, "Test") && isTestSignature(sign) {
  1828. return true // don't break tests
  1829. }
  1830. default:
  1831. return true // we only want to rename the above
  1832. }
  1833. node.Name = hashWithPackage(lpkg, name)
  1834. // TODO: probably move the debugf lines inside the hash funcs
  1835. if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed
  1836. log.Printf("%s %q hashed with %x… to %q", debugName, name, hashToUse[:4], node.Name)
  1837. }
  1838. return true
  1839. }
  1840. post := func(cursor *astutil.Cursor) bool {
  1841. imp, ok := cursor.Node().(*ast.ImportSpec)
  1842. if !ok {
  1843. return true
  1844. }
  1845. path, err := strconv.Unquote(imp.Path.Value)
  1846. if err != nil {
  1847. panic(err) // should never happen
  1848. }
  1849. // We're importing an obfuscated package.
  1850. // Replace the import path with its obfuscated version.
  1851. // If the import was unnamed, give it the name of the
  1852. // original package name, to keep references working.
  1853. lpkg, err := listPackage(tf.curPkg, path)
  1854. if err != nil {
  1855. panic(err) // should never happen
  1856. }
  1857. if !lpkg.ToObfuscate {
  1858. return true
  1859. }
  1860. if lpkg.Name != "main" {
  1861. newPath := lpkg.obfuscatedImportPath()
  1862. imp.Path.Value = strconv.Quote(newPath)
  1863. }
  1864. if imp.Name == nil {
  1865. imp.Name = &ast.Ident{
  1866. NamePos: imp.Path.ValuePos, // ensure it ends up on the same line
  1867. Name: lpkg.Name,
  1868. }
  1869. }
  1870. return true
  1871. }
  1872. return astutil.Apply(file, pre, post).(*ast.File)
  1873. }
  1874. // named tries to obtain the *types.Named behind a type, if there is one.
  1875. // This is useful to obtain "testing.T" from "*testing.T", or to obtain the type
  1876. // declaration object from an embedded field.
  1877. func namedType(t types.Type) *types.Named {
  1878. switch t := t.(type) {
  1879. case *types.Named:
  1880. return t
  1881. case interface{ Elem() types.Type }:
  1882. return namedType(t.Elem())
  1883. default:
  1884. return nil
  1885. }
  1886. }
  1887. // isTestSignature returns true if the signature matches "func _(*testing.T)".
  1888. func isTestSignature(sign *types.Signature) bool {
  1889. if sign.Recv() != nil {
  1890. return false // test funcs don't have receivers
  1891. }
  1892. params := sign.Params()
  1893. if params.Len() != 1 {
  1894. return false // too many parameters for a test func
  1895. }
  1896. named := namedType(params.At(0).Type())
  1897. if named == nil {
  1898. return false // the only parameter isn't named, like "string"
  1899. }
  1900. obj := named.Obj()
  1901. return obj != nil && obj.Pkg().Path() == "testing" && obj.Name() == "T"
  1902. }
  1903. func (tf *transformer) transformLink(args []string) ([]string, error) {
  1904. // We can't split by the ".a" extension, because cached object files
  1905. // lack any extension.
  1906. flags, args := splitFlagsFromArgs(args)
  1907. newImportCfg, err := tf.processImportCfg(flags, nil)
  1908. if err != nil {
  1909. return nil, err
  1910. }
  1911. // TODO: unify this logic with the -X handling when using -literals.
  1912. // We should be able to handle both cases via the syntax tree.
  1913. //
  1914. // Make sure -X works with obfuscated identifiers.
  1915. // To cover both obfuscated and non-obfuscated names,
  1916. // duplicate each flag with a obfuscated version.
  1917. flagValueIter(flags, "-X", func(val string) {
  1918. // val is in the form of "foo.com/bar.name=value".
  1919. fullName, stringValue, found := strings.Cut(val, "=")
  1920. if !found {
  1921. return // invalid
  1922. }
  1923. // fullName is "foo.com/bar.name"
  1924. i := strings.LastIndexByte(fullName, '.')
  1925. path, name := fullName[:i], fullName[i+1:]
  1926. // If the package path is "main", it's the current top-level
  1927. // package we are linking.
  1928. // Otherwise, find it in the cache.
  1929. lpkg := tf.curPkg
  1930. if path != "main" {
  1931. lpkg = sharedCache.ListedPackages[path]
  1932. }
  1933. if lpkg == nil {
  1934. // We couldn't find the package.
  1935. // Perhaps a typo, perhaps not part of the build.
  1936. // cmd/link ignores those, so we should too.
  1937. return
  1938. }
  1939. // As before, the main package must remain as "main".
  1940. newPath := path
  1941. if path != "main" {
  1942. newPath = lpkg.obfuscatedImportPath()
  1943. }
  1944. newName := hashWithPackage(lpkg, name)
  1945. flags = append(flags, fmt.Sprintf("-X=%s.%s=%s", newPath, newName, stringValue))
  1946. })
  1947. // Starting in Go 1.17, Go's version is implicitly injected by the linker.
  1948. // It's the same method as -X, so we can override it with an extra flag.
  1949. flags = append(flags, "-X=runtime.buildVersion=unknown")
  1950. // Ensure we strip the -buildid flag, to not leak any build IDs for the
  1951. // link operation or the main package's compilation.
  1952. flags = flagSetValue(flags, "-buildid", "")
  1953. // Strip debug information and symbol tables.
  1954. flags = append(flags, "-w", "-s")
  1955. flags = flagSetValue(flags, "-importcfg", newImportCfg)
  1956. return append(flags, args...), nil
  1957. }
  1958. func splitFlagsFromArgs(all []string) (flags, args []string) {
  1959. for i := 0; i < len(all); i++ {
  1960. arg := all[i]
  1961. if !strings.HasPrefix(arg, "-") {
  1962. return all[:i:i], all[i:]
  1963. }
  1964. if booleanFlags[arg] || strings.Contains(arg, "=") {
  1965. // Either "-bool" or "-name=value".
  1966. continue
  1967. }
  1968. // "-name value", so the next arg is part of this flag.
  1969. i++
  1970. }
  1971. return all, nil
  1972. }
  1973. func alterTrimpath(flags []string) []string {
  1974. trimpath := flagValue(flags, "-trimpath")
  1975. // Add our temporary dir to the beginning of -trimpath, so that we don't
  1976. // leak temporary dirs. Needs to be at the beginning, since there may be
  1977. // shorter prefixes later in the list, such as $PWD if TMPDIR=$PWD/tmp.
  1978. return flagSetValue(flags, "-trimpath", sharedTempDir+"=>;"+trimpath)
  1979. }
  1980. // forwardBuildFlags is obtained from 'go help build' as of Go 1.20.
  1981. var forwardBuildFlags = map[string]bool{
  1982. // These shouldn't be used in nested cmd/go calls.
  1983. "-a": false,
  1984. "-n": false,
  1985. "-x": false,
  1986. "-v": false,
  1987. // These are always set by garble.
  1988. "-trimpath": false,
  1989. "-toolexec": false,
  1990. "-buildvcs": false,
  1991. "-C": true,
  1992. "-asan": true,
  1993. "-asmflags": true,
  1994. "-buildmode": true,
  1995. "-compiler": true,
  1996. "-cover": true,
  1997. "-covermode": true,
  1998. "-coverpkg": true,
  1999. "-gccgoflags": true,
  2000. "-gcflags": true,
  2001. "-installsuffix": true,
  2002. "-ldflags": true,
  2003. "-linkshared": true,
  2004. "-mod": true,
  2005. "-modcacherw": true,
  2006. "-modfile": true,
  2007. "-msan": true,
  2008. "-overlay": true,
  2009. "-p": true,
  2010. "-pgo": true,
  2011. "-pkgdir": true,
  2012. "-race": true,
  2013. "-tags": true,
  2014. "-work": true,
  2015. "-workfile": true,
  2016. }
  2017. // booleanFlags is obtained from 'go help build' and 'go help testflag' as of Go 1.20.
  2018. var booleanFlags = map[string]bool{
  2019. // Shared build flags.
  2020. "-a": true,
  2021. "-asan": true,
  2022. "-buildvcs": true,
  2023. "-cover": true,
  2024. "-i": true,
  2025. "-linkshared": true,
  2026. "-modcacherw": true,
  2027. "-msan": true,
  2028. "-n": true,
  2029. "-race": true,
  2030. "-trimpath": true,
  2031. "-v": true,
  2032. "-work": true,
  2033. "-x": true,
  2034. // Test flags (TODO: support its special -args flag)
  2035. "-benchmem": true,
  2036. "-c": true,
  2037. "-failfast": true,
  2038. "-fullpath": true,
  2039. "-json": true,
  2040. "-short": true,
  2041. }
  2042. func filterForwardBuildFlags(flags []string) (filtered []string, firstUnknown string) {
  2043. for i := 0; i < len(flags); i++ {
  2044. arg := flags[i]
  2045. if strings.HasPrefix(arg, "--") {
  2046. arg = arg[1:] // "--name" to "-name"; keep the short form
  2047. }
  2048. name, _, _ := strings.Cut(arg, "=") // "-name=value" to "-name"
  2049. buildFlag := forwardBuildFlags[name]
  2050. if buildFlag {
  2051. filtered = append(filtered, arg)
  2052. } else {
  2053. firstUnknown = name
  2054. }
  2055. if booleanFlags[arg] || strings.Contains(arg, "=") {
  2056. // Either "-bool" or "-name=value".
  2057. continue
  2058. }
  2059. // "-name value", so the next arg is part of this flag.
  2060. if i++; buildFlag && i < len(flags) {
  2061. filtered = append(filtered, flags[i])
  2062. }
  2063. }
  2064. return filtered, firstUnknown
  2065. }
  2066. // splitFlagsFromFiles splits args into a list of flag and file arguments. Since
  2067. // we can't rely on "--" being present, and we don't parse all flags upfront, we
  2068. // rely on finding the first argument that doesn't begin with "-" and that has
  2069. // the extension we expect for the list of paths.
  2070. //
  2071. // This function only makes sense for lower-level tool commands, such as
  2072. // "compile" or "link", since their arguments are predictable.
  2073. //
  2074. // We iterate from the end rather than from the start, to better protect
  2075. // oursrelves from flag arguments that may look like paths, such as:
  2076. //
  2077. // compile [flags...] -p pkg/path.go [more flags...] file1.go file2.go
  2078. //
  2079. // For now, since those confusing flags are always followed by more flags,
  2080. // iterating in reverse order works around them entirely.
  2081. func splitFlagsFromFiles(all []string, ext string) (flags, paths []string) {
  2082. for i := len(all) - 1; i >= 0; i-- {
  2083. arg := all[i]
  2084. if strings.HasPrefix(arg, "-") || !strings.HasSuffix(arg, ext) {
  2085. cutoff := i + 1 // arg is a flag, not a path
  2086. return all[:cutoff:cutoff], all[cutoff:]
  2087. }
  2088. }
  2089. return nil, all
  2090. }
  2091. // flagValue retrieves the value of a flag such as "-foo", from strings in the
  2092. // list of arguments like "-foo=bar" or "-foo" "bar". If the flag is repeated,
  2093. // the last value is returned.
  2094. func flagValue(flags []string, name string) string {
  2095. lastVal := ""
  2096. flagValueIter(flags, name, func(val string) {
  2097. lastVal = val
  2098. })
  2099. return lastVal
  2100. }
  2101. // flagValueIter retrieves all the values for a flag such as "-foo", like
  2102. // flagValue. The difference is that it allows handling complex flags, such as
  2103. // those whose values compose a list.
  2104. func flagValueIter(flags []string, name string, fn func(string)) {
  2105. for i, arg := range flags {
  2106. if val := strings.TrimPrefix(arg, name+"="); val != arg {
  2107. // -name=value
  2108. fn(val)
  2109. }
  2110. if arg == name { // -name ...
  2111. if i+1 < len(flags) {
  2112. // -name value
  2113. fn(flags[i+1])
  2114. }
  2115. }
  2116. }
  2117. }
  2118. func flagSetValue(flags []string, name, value string) []string {
  2119. for i, arg := range flags {
  2120. if strings.HasPrefix(arg, name+"=") {
  2121. // -name=value
  2122. flags[i] = name + "=" + value
  2123. return flags
  2124. }
  2125. if arg == name { // -name ...
  2126. if i+1 < len(flags) {
  2127. // -name value
  2128. flags[i+1] = value
  2129. return flags
  2130. }
  2131. return flags
  2132. }
  2133. }
  2134. return append(flags, name+"="+value)
  2135. }
  2136. func fetchGoEnv() error {
  2137. out, err := exec.Command("go", "env", "-json",
  2138. // Keep in sync with sharedCache.GoEnv.
  2139. "GOOS", "GOMOD", "GOVERSION", "GOROOT",
  2140. ).CombinedOutput()
  2141. if err != nil {
  2142. // TODO: cover this in the tests.
  2143. fmt.Fprintf(os.Stderr, `Can't find the Go toolchain: %v
  2144. This is likely due to Go not being installed/setup correctly.
  2145. To install Go, see: https://go.dev/doc/install
  2146. `, err)
  2147. return errJustExit(1)
  2148. }
  2149. if err := json.Unmarshal(out, &sharedCache.GoEnv); err != nil {
  2150. return fmt.Errorf(`cannot unmarshal from "go env -json": %w`, err)
  2151. }
  2152. sharedCache.GOGARBLE = os.Getenv("GOGARBLE")
  2153. if sharedCache.GOGARBLE == "" {
  2154. sharedCache.GOGARBLE = "*" // we default to obfuscating everything
  2155. }
  2156. return nil
  2157. }