This package is in charge of the FIPS init-time code+data verification.
If GODEBUG=fips140=off or the empty string, then no verification
happens. Otherwise, the setting must be "on", "debug", or "only",
all of which enable verification. If the setting is "debug", successful
verification prints a message to that effect. Otherwise successful
verification is quiet.
The linker leaves special information for this package to use.
See cmd/internal/obj/fips.go and cmd/link/internal/ld/fips.go,
both submitted in earlier CLs, for details.
For #69536.
Change-Id: Ie1fe29f316db290e0bd7df0a5a09108be4779d63
Reviewed-on: https://go-review.googlesource.com/c/go/+/625998
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
--- /dev/null
+// Copyright 2024 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.
+
+// Package check implements the FIPS-140 load-time code+data verification.
+// Every FIPS package providing cryptographic functionality except hmac and sha256
+// must import crypto/internal/fips/check, so that the verification happens
+// before initialization of package global variables.
+// The hmac and sha256 packages are used by this package, so they cannot import it.
+// Instead, those packages must be careful not to change global variables during init.
+// (If necessary, we could have check call a PostCheck function in those packages
+// after the check has completed.)
+package check
+
+import (
+ "crypto/internal/fips/hmac"
+ "crypto/internal/fips/sha256"
+ "internal/byteorder"
+ "internal/godebug"
+ "io"
+ "runtime"
+ "unsafe"
+)
+
+// Enabled reports whether verification was enabled.
+// If Enabled returns true, then verification succeeded,
+// because if it failed the binary would have panicked at init time.
+func Enabled() bool {
+ return enabled
+}
+
+var enabled bool // set when verification is enabled
+var verified bool // set when verification succeeds, for testing
+
+// supported reports whether the current GOOS/GOARCH is supported at all.
+func supported() bool {
+ // See cmd/internal/obj/fips.go's EnableFIPS for commentary.
+ switch {
+ case runtime.GOARCH == "wasm",
+ runtime.GOOS == "windows" && runtime.GOARCH == "386",
+ runtime.GOOS == "windows" && runtime.GOARCH == "arm",
+ runtime.GOOS == "windows" && runtime.GOARCH == "arm64":
+ return false
+ }
+ return true
+}
+
+// linkinfo holds the go:fipsinfo symbol prepared by the linker.
+// See cmd/link/internal/ld/fips.go for details.
+//
+//go:linkname linkinfo go:fipsinfo
+var linkinfo struct {
+ Magic [16]byte
+ Sum [32]byte
+ Self uintptr
+ Sects [4]struct {
+ // Note: These must be unsafe.Pointer, not uintptr,
+ // or else checkptr panics about turning uintptrs
+ // into pointers into the data segment during
+ // go test -race.
+ Start unsafe.Pointer
+ End unsafe.Pointer
+ }
+}
+
+// "\xff"+fipsMagic is the expected linkinfo.Magic.
+// We avoid writing that explicitly so that the string does not appear
+// elsewhere in normal binaries, just as a precaution.
+const fipsMagic = " Go fipsinfo \xff\x00"
+
+var zeroSum [32]byte
+
+func init() {
+ v := godebug.New("#fips140").Value()
+ enabled = v != "" && v != "off"
+ if !enabled {
+ return
+ }
+
+ switch v {
+ case "on", "only", "debug":
+ // ok
+ default:
+ panic("fips140: unknown GODEBUG setting fips140=" + v)
+ }
+
+ if !supported() {
+ panic("fips140: unavailable on " + runtime.GOOS + "-" + runtime.GOARCH)
+ }
+
+ if linkinfo.Magic[0] != 0xff || string(linkinfo.Magic[1:]) != fipsMagic || linkinfo.Sum == zeroSum {
+ panic("fips140: no verification checksum found")
+ }
+
+ h := hmac.New(sha256.New, make([]byte, 32))
+ w := io.Writer(h)
+
+ /*
+ // Uncomment for debugging.
+ // Commented (as opposed to a const bool flag)
+ // to avoid import "os" in default builds.
+ f, err := os.Create("fipscheck.o")
+ if err != nil {
+ panic(err)
+ }
+ w = io.MultiWriter(h, f)
+ */
+
+ w.Write([]byte("go fips object v1\n"))
+
+ var nbuf [8]byte
+ for _, sect := range linkinfo.Sects {
+ n := uintptr(sect.End) - uintptr(sect.Start)
+ byteorder.BePutUint64(nbuf[:], uint64(n))
+ w.Write(nbuf[:])
+ w.Write(unsafe.Slice((*byte)(sect.Start), n))
+ }
+ sum := h.Sum(nil)
+
+ if [32]byte(sum) != linkinfo.Sum {
+ panic("fips140: verification mismatch")
+ }
+
+ if v == "debug" {
+ println("fips140: verified code+data")
+ }
+
+ verified = true
+}
--- /dev/null
+// Copyright 2024 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.
+
+package check_test
+
+import (
+ . "crypto/internal/fips/check"
+ "crypto/internal/fips/check/checktest"
+ "fmt"
+ "internal/abi"
+ "internal/godebug"
+ "os"
+ "os/exec"
+ "runtime"
+ "testing"
+ "unicode"
+ "unsafe"
+)
+
+const enableFIPSTest = false
+
+func TestVerify(t *testing.T) {
+ if *Verified {
+ t.Logf("verified")
+ return
+ }
+
+ if godebug.New("#fips140").Value() == "on" {
+ t.Fatalf("GODEBUG=fips140=on but verification did not run")
+ }
+
+ if !enableFIPSTest {
+ return
+ }
+
+ if !Supported() {
+ t.Skipf("skipping on %s-%s", runtime.GOOS, runtime.GOARCH)
+ }
+
+ cmd := exec.Command(os.Args[0], "-test.v")
+ cmd.Env = append(cmd.Environ(), "GODEBUG=fips140=on")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("GODEBUG=fips140=on %v failed: %v\n%s", cmd.Args, err, out)
+ }
+ t.Logf("exec'ed GODEBUG=fips140=on and succeeded:\n%s", out)
+}
+
+func TestInfo(t *testing.T) {
+ if !enableFIPSTest {
+ return
+ }
+
+ if !Supported() {
+ t.Skipf("skipping on %s-%s", runtime.GOOS, runtime.GOARCH)
+ }
+
+ // Check that the checktest symbols are initialized properly.
+ if checktest.NOPTRDATA != 1 {
+ t.Errorf("checktest.NOPTRDATA = %d, want 1", checktest.NOPTRDATA)
+ }
+ if checktest.RODATA != 2 {
+ t.Errorf("checktest.RODATA = %d, want 2", checktest.RODATA)
+ }
+ if checktest.DATA.P != &checktest.NOPTRDATA {
+ t.Errorf("checktest.DATA.P = %p, want &checktest.NOPTRDATA (%p)", checktest.DATA.P, &checktest.NOPTRDATA)
+ }
+ if checktest.DATA.X != 3 {
+ t.Errorf("checktest.DATA.X = %d, want 3", checktest.DATA.X)
+ }
+ if checktest.NOPTRBSS != 0 {
+ t.Errorf("checktest.NOPTRBSS = %d, want 0", checktest.NOPTRBSS)
+ }
+ if checktest.BSS != nil {
+ t.Errorf("checktest.BSS = %p, want nil", checktest.BSS)
+ }
+
+ // Check that the checktest symbols are in the right go:fipsinfo sections.
+ sect := func(i int, name string, p unsafe.Pointer) {
+ s := Linkinfo.Sects[i]
+ if !(uintptr(s.Start) <= uintptr(p) && uintptr(p) < uintptr(s.End)) {
+ t.Errorf("checktest.%s (%#x) not in section #%d (%#x..%#x)", name, p, i, s.Start, s.End)
+ }
+ }
+ sect(0, "TEXT", unsafe.Pointer(abi.FuncPCABIInternal(checktest.TEXT)))
+ sect(1, "RODATA", unsafe.Pointer(&checktest.RODATA))
+ sect(2, "NOPTRDATA", unsafe.Pointer(&checktest.NOPTRDATA))
+ sect(3, "DATA", unsafe.Pointer(&checktest.DATA))
+
+ // Check that some symbols are not in FIPS sections.
+ no := func(name string, p unsafe.Pointer, ix ...int) {
+ for _, i := range ix {
+ s := Linkinfo.Sects[i]
+ if uintptr(s.Start) <= uintptr(p) && uintptr(p) < uintptr(s.End) {
+ t.Errorf("%s (%#x) unexpectedly in section #%d (%#x..%#x)", name, p, i, s.Start, s.End)
+ }
+ }
+ }
+
+ // Check that the symbols are not in unexpected sections (that is, no overlaps).
+ no("checktest.TEXT", unsafe.Pointer(abi.FuncPCABIInternal(checktest.TEXT)), 1, 2, 3)
+ no("checktest.RODATA", unsafe.Pointer(&checktest.RODATA), 0, 2, 3)
+ no("checktest.NOPTRDATA", unsafe.Pointer(&checktest.NOPTRDATA), 0, 1, 3)
+ no("checktest.DATA", unsafe.Pointer(&checktest.DATA), 0, 1, 2)
+
+ // Check that non-FIPS symbols are not in any of the sections.
+ no("fmt.Printf", unsafe.Pointer(abi.FuncPCABIInternal(fmt.Printf)), 0, 1, 2, 3) // TEXT
+ no("unicode.Categories", unsafe.Pointer(&unicode.Categories), 0, 1, 2, 3) // BSS
+ no("unicode.ASCII_Hex_Digit", unsafe.Pointer(&unicode.ASCII_Hex_Digit), 0, 1, 2, 3) // DATA
+
+ // Check that we have enough data in total.
+ // On arm64 the fips sections in this test currently total 23 kB.
+ n := uintptr(0)
+ for _, s := range Linkinfo.Sects {
+ n += uintptr(s.End) - uintptr(s.Start)
+ }
+ if n < 16*1024 {
+ t.Fatalf("fips sections not big enough: %d, want at least 16 kB", n)
+ }
+}
--- /dev/null
+//go:build !purego && !wasm
+
+#include "textflag.h"
+
+DATA ·RODATA(SB)/4, $2
+GLOBL ·RODATA(SB), RODATA, $4
--- /dev/null
+// Copyright 2024 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.
+
+// Package checktest defines some code and data for use in
+// the crypto/internal/fips/check test.
+package checktest
+
+import _ "crypto/internal/fips/check"
+
+var NOPTRDATA int = 1
+
+var RODATA int32 // set to 2 in asm.s
+
+// DATA needs to have both a pointer and an int so that _some_ of it gets
+// initialized at link time, so it is treated as DATA and not BSS.
+// The pointer is deferred to init time.
+var DATA = struct {
+ P *int
+ X int
+}{&NOPTRDATA, 3}
+
+var NOPTRBSS int
+
+var BSS *int
+
+func TEXT() {}
--- /dev/null
+// Copyright 2024 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.
+
+package check
+
+var Verified = &verified
+var Linkinfo = &linkinfo
+var Supported = supported
import (
"crypto"
"crypto/internal/boring"
+ _ "crypto/internal/fips/check"
"crypto/internal/fips/sha256"
"hash"
)
// The scan (if any) should be limited to the [16]byte.
d := new(cgoData)
d.Ptr = d
+ _ = d.Ptr // for unusedwrite check
h := New()
h.Write(d.Data[:])
h.Sum(nil)
< crypto/internal/fips/sha512
< crypto/internal/fips/sha3
< crypto/internal/fips/hmac
+ < crypto/internal/fips/check
< FIPS;
+ FIPS < crypto/internal/fips/check/checktest;
+
NONE < crypto/internal/boring/sig, crypto/internal/boring/syso;
sync/atomic < crypto/internal/boring/bcache, crypto/internal/boring/fipstls;
crypto/internal/boring/sig, crypto/internal/boring/fipstls < crypto/tls/fipsonly;