// SPDX-License-Identifier: Apache-2.0 // // Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Command go-overlay prepares the `go build -overlay` file used for release // builds and verifies its effect on the built executables. // // Go 1.26 added a 32 MiB package-level scratch buffer to the FIPS 140-3 // entropy source (crypto/internal/fips140/drbg.memory). On Windows it is part // of the writable data section of the executable image, which costs 32 MiB of // commit charge per process even when FIPS 140-3 mode is disabled. The overlay // replaces that file with a copy that allocates the buffer on first use. // See https://github.com/golang/go/issues/81956. // // Usage: // // go run ./tools/go-overlay generate // go run ./tools/go-overlay verify ... // // generate writes overlay.json and the patched source file to dir. Pass // -overlay=/overlay.json to go build. verify fails if an executable still // contains the package-level buffer. package main import ( "bytes" "context" "crypto/sha256" "debug/pe" _ "embed" "encoding/hex" "encoding/json" "errors" "fmt" "os" "os/exec" "path/filepath" "strings" ) const ( // targetFile is the Go standard library file replaced by the overlay. targetFile = "src/crypto/internal/fips140/drbg/entropy_fips140.go" // targetSHA256 is the checksum of targetFile in the Go version the // overlay was written for. A Go update that changes the file must fail // the build, so the overlay is re-checked or dropped once Go fixes // golang/go#81956. targetSHA256 = "bd3c834a29e31c93e56d81da54d4a874561814a86550de1456c8d6d40a9c5fda" // bufferSymbol is the package-level buffer removed by the overlay. bufferSymbol = "crypto/internal/fips140/drbg.memory" ) //go:embed entropy_fips140.go.overlay var patchedSource []byte func main() { if err := run(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, "go-overlay:", err) os.Exit(1) } } func run(args []string) error { if len(args) < 2 { return errors.New("usage: go-overlay generate | verify [ ...]") } switch args[0] { case "generate": if len(args) != 2 { return errors.New("usage: go-overlay generate ") } return generate(context.Background(), args[1]) case "verify": for _, path := range args[1:] { if err := verify(path); err != nil { return err } } return nil default: return fmt.Errorf("unknown command %q", args[0]) } } func generate(ctx context.Context, dir string) error { goroot, err := goEnv(ctx, "GOROOT") if err != nil { return err } target := filepath.Join(goroot, filepath.FromSlash(targetFile)) original, err := os.ReadFile(target) if err != nil { return fmt.Errorf("read %s: %w", target, err) } sum := sha256.Sum256(original) if got := hex.EncodeToString(sum[:]); got != targetSHA256 { return fmt.Errorf("%s has checksum %s, expected %s: the Go toolchain changed this file, "+ "so update tools/go-overlay/entropy_fips140.go.overlay from the new version "+ "or remove the overlay if golang/go#81956 is fixed", target, got, targetSHA256) } // The go command rejects overlays for files in the module cache, which is // where toolchains downloaded through GOTOOLCHAIN are stored. gomodcache, err := goEnv(ctx, "GOMODCACHE") if err != nil { return err } if gomodcache != "" && isWithin(goroot, gomodcache) { return fmt.Errorf("GOROOT %s is inside GOMODCACHE %s, where go build does not allow overlays; "+ "install the Go version from go.mod instead of using a downloaded toolchain", goroot, gomodcache) } dir, err = filepath.Abs(dir) if err != nil { return err } //nolint:gosec // The directory is chosen by the caller of this build tool. if err := os.MkdirAll(dir, 0o755); err != nil { return err } patched := filepath.Join(dir, "entropy_fips140.go") //nolint:gosec // Build input, not a secret. if err := os.WriteFile(patched, patchedSource, 0o644); err != nil { return err } overlay, err := json.MarshalIndent(map[string]map[string]string{ "Replace": {target: patched}, }, "", " ") if err != nil { return err } return os.WriteFile(filepath.Join(dir, "overlay.json"), append(overlay, '\n'), 0o644) //nolint:gosec // Build input, not a secret. } func verify(path string) error { f, err := pe.Open(path) if err != nil { return fmt.Errorf("open %s: %w", path, err) } defer f.Close() if len(f.Symbols) == 0 { return fmt.Errorf("%s has no symbol table, cannot verify the overlay", path) } for _, sym := range f.Symbols { if sym.Name == bufferSymbol { return fmt.Errorf("%s contains %s: the go-overlay was not applied", path, bufferSymbol) } } return nil } func goEnv(ctx context.Context, name string) (string, error) { var stderr bytes.Buffer cmd := exec.CommandContext(ctx, "go", "env", name) cmd.Stderr = &stderr out, err := cmd.Output() if err != nil { return "", fmt.Errorf("go env %s: %w: %s", name, err, stderr.String()) } return strings.TrimSpace(string(out)), nil } func isWithin(path, dir string) bool { rel, err := filepath.Rel(dir, path) return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) }