mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-21 03:59:04 +02:00
fix: drop the 32 MiB FIPS entropy buffer from release binaries (#2667)
Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
96fdb6f779
commit
d8f281ba97
@@ -99,6 +99,9 @@ jobs:
|
|||||||
version: v2.18.3
|
version: v2.18.3
|
||||||
args: build --clean
|
args: build --clean
|
||||||
|
|
||||||
|
- name: Verify Go overlay
|
||||||
|
run: go run ./tools/go-overlay verify dist/windows_exporter-amd64.exe dist/windows_exporter-arm64.exe
|
||||||
|
|
||||||
- name: Stage build artifacts
|
- name: Stage build artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir -p output
|
mkdir -p output
|
||||||
|
|||||||
@@ -23,3 +23,4 @@ local/
|
|||||||
!/.idea/copyright/windows_exporter.xml
|
!/.idea/copyright/windows_exporter.xml
|
||||||
!/.idea/vcs.xml
|
!/.idea/vcs.xml
|
||||||
!/.idea/go.imports.xml
|
!/.idea/go.imports.xml
|
||||||
|
/.go-overlay/
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
# Removes the 32 MiB FIPS 140-3 entropy buffer that Go 1.26+ links into
|
||||||
|
# every binary. See tools/go-overlay/main.go.
|
||||||
|
- go run ./tools/go-overlay generate .go-overlay
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: windows_exporter
|
- id: windows_exporter
|
||||||
main: ./cmd/windows_exporter
|
main: ./cmd/windows_exporter
|
||||||
@@ -16,6 +22,7 @@ builds:
|
|||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
flags:
|
flags:
|
||||||
- -trimpath
|
- -trimpath
|
||||||
|
- -overlay=.go-overlay/overlay.json
|
||||||
tags:
|
tags:
|
||||||
- trimpath
|
- trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
|
|||||||
@@ -65,6 +65,13 @@ go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 run ./...
|
|||||||
go test ./...
|
go test ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Release builds apply a `go build -overlay` from
|
||||||
|
[`tools/go-overlay`](tools/go-overlay/main.go) that removes the 32 MiB FIPS
|
||||||
|
140-3 entropy buffer Go links into every binary (golang/go#81956). It is pinned
|
||||||
|
to the checksum of the patched standard library file, so a Go update that
|
||||||
|
changes that file fails the build job until the overlay is refreshed or, once Go
|
||||||
|
fixes the issue, removed. Plain `go build` and `make build` do not apply it.
|
||||||
|
|
||||||
For collector or concurrency changes, also run the affected packages natively
|
For collector or concurrency changes, also run the affected packages natively
|
||||||
on Windows with `go test -race -count=1 -timeout=10m <packages>`. Add regression
|
on Windows with `go test -race -count=1 -timeout=10m <packages>`. Add regression
|
||||||
tests for behavior changes and benchmarks for performance improvements. Check
|
tests for behavior changes and benchmarks for performance improvements. Check
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2026 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Patched copy of $GOROOT/src/crypto/internal/fips140/drbg/entropy_fips140.go
|
||||||
|
// from Go 1.27.2, applied by tools/go-overlay. The only change is that the
|
||||||
|
// 32 MiB entropy scratch buffer is allocated on first use instead of being a
|
||||||
|
// package-level variable. On Windows, the package-level variable is part of
|
||||||
|
// the writable .data section of the executable image, which is charged as
|
||||||
|
// commit for every process even if FIPS 140-3 mode is never enabled.
|
||||||
|
// See https://github.com/golang/go/issues/81956.
|
||||||
|
//
|
||||||
|
//go:build !wasm
|
||||||
|
|
||||||
|
package drbg
|
||||||
|
|
||||||
|
import (
|
||||||
|
entropy "crypto/internal/entropy/v1.0.0"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scratchMemory returns a scratch buffer that is accessed between samples by the
|
||||||
|
// entropy source to expose it to memory access timings.
|
||||||
|
//
|
||||||
|
// It is allocated once and shared between Seed calls to avoid the cost of
|
||||||
|
// zeroing a new allocation every time. The entropy source accesses it using
|
||||||
|
// atomics (and doesn't care about its contents).
|
||||||
|
var scratchMemory = sync.OnceValue(func() *entropy.ScratchBuffer {
|
||||||
|
return new(entropy.ScratchBuffer)
|
||||||
|
})
|
||||||
|
|
||||||
|
func getEntropy() *[SeedSize]byte {
|
||||||
|
var retries int
|
||||||
|
seed, err := entropy.Seed(scratchMemory())
|
||||||
|
for err != nil {
|
||||||
|
// The CPU jitter-based SP 800-90B entropy source has a non-negligible
|
||||||
|
// chance of failing the startup health tests.
|
||||||
|
//
|
||||||
|
// Each time it does, it enters a permanent failure state, and we
|
||||||
|
// restart it anew. This is not expected to happen more than a few times
|
||||||
|
// in a row.
|
||||||
|
if retries++; retries > 100 {
|
||||||
|
panic("fips140/drbg: failed to obtain initial entropy")
|
||||||
|
}
|
||||||
|
seed, err = entropy.Seed(scratchMemory())
|
||||||
|
}
|
||||||
|
return &seed
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
// 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 <dir>
|
||||||
|
// go run ./tools/go-overlay verify <executable>...
|
||||||
|
//
|
||||||
|
// generate writes overlay.json and the patched source file to dir. Pass
|
||||||
|
// -overlay=<dir>/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 <dir> | verify <executable> [<executable> ...]")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case "generate":
|
||||||
|
if len(args) != 2 {
|
||||||
|
return errors.New("usage: go-overlay generate <dir>")
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user