]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/internal/fips/check: add new package
authorRuss Cox <rsc@golang.org>
Tue, 5 Nov 2024 18:52:13 +0000 (13:52 -0500)
committerRuss Cox <rsc@golang.org>
Wed, 13 Nov 2024 10:42:11 +0000 (10:42 +0000)
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>
src/crypto/internal/fips/check/check.go [new file with mode: 0644]
src/crypto/internal/fips/check/check_test.go [new file with mode: 0644]
src/crypto/internal/fips/check/checktest/asm.s [new file with mode: 0644]
src/crypto/internal/fips/check/checktest/test.go [new file with mode: 0644]
src/crypto/internal/fips/check/export_test.go [new file with mode: 0644]
src/crypto/sha256/sha256.go
src/crypto/sha256/sha256_test.go
src/go/build/deps_test.go

diff --git a/src/crypto/internal/fips/check/check.go b/src/crypto/internal/fips/check/check.go
new file mode 100644 (file)
index 0000000..7c1d788
--- /dev/null
@@ -0,0 +1,129 @@
+// 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
+}
diff --git a/src/crypto/internal/fips/check/check_test.go b/src/crypto/internal/fips/check/check_test.go
new file mode 100644 (file)
index 0000000..a551f7c
--- /dev/null
@@ -0,0 +1,121 @@
+// 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)
+       }
+}
diff --git a/src/crypto/internal/fips/check/checktest/asm.s b/src/crypto/internal/fips/check/checktest/asm.s
new file mode 100644 (file)
index 0000000..1151a13
--- /dev/null
@@ -0,0 +1,6 @@
+//go:build !purego && !wasm
+
+#include "textflag.h"
+
+DATA ·RODATA(SB)/4, $2
+GLOBL ·RODATA(SB), RODATA, $4
diff --git a/src/crypto/internal/fips/check/checktest/test.go b/src/crypto/internal/fips/check/checktest/test.go
new file mode 100644 (file)
index 0000000..b234316
--- /dev/null
@@ -0,0 +1,27 @@
+// 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() {}
diff --git a/src/crypto/internal/fips/check/export_test.go b/src/crypto/internal/fips/check/export_test.go
new file mode 100644 (file)
index 0000000..e713970
--- /dev/null
@@ -0,0 +1,9 @@
+// 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
index d87c689c9001ad0d3cd5316c77ac87df10b7eb1d..3c3aba84c586ce931f3fddc6c9be9be1b666964d 100644 (file)
@@ -9,6 +9,7 @@ package sha256
 import (
        "crypto"
        "crypto/internal/boring"
+       _ "crypto/internal/fips/check"
        "crypto/internal/fips/sha256"
        "hash"
 )
index ffd163865158300967c0f8d5094859d0bd31e630..77617a41a2a1f3662df38459937d85d56ad2bd5d 100644 (file)
@@ -336,6 +336,7 @@ func TestCgo(t *testing.T) {
        // 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)
index 44f98aafb24a9f50b185efb1b771c6a9750f2662..40d8ab1ba71180351ceecb4e06e4bf9d96487c2b 100644 (file)
@@ -457,8 +457,11 @@ var depsRules = `
        < 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;