blob: 2ceef6dd883d0ab8244eb2254c62f04b44498e76 [file] [log] [blame] [edit]
// 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 fipstest
import (
"bytes"
"crypto/internal/cryptotest"
. "crypto/internal/fips140/check"
"crypto/internal/fips140/check/checktest"
"fmt"
"internal/abi"
"internal/godebug"
"internal/testenv"
"os"
"path/filepath"
"runtime"
"testing"
"unicode"
"unsafe"
)
func TestIntegrityCheck(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")
}
cryptotest.MustSupportFIPS140(t)
cmd := testenv.Command(t, testenv.Executable(t), "-test.v", "-test.run=^TestIntegrityCheck$")
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 TestIntegrityCheckFailure(t *testing.T) {
moduleStatus(t)
testenv.MustHaveExec(t)
cryptotest.MustSupportFIPS140(t)
bin, err := os.ReadFile(os.Args[0])
if err != nil {
t.Fatal(err)
}
// Replace the expected module checksum with a different value.
bin = bytes.ReplaceAll(bin, Linkinfo.Sum[:], bytes.Repeat([]byte("X"), len(Linkinfo.Sum)))
binPath := filepath.Join(t.TempDir(), "fips140test.exe")
if err := os.WriteFile(binPath, bin, 0o755); err != nil {
t.Fatal(err)
}
if runtime.GOOS == "darwin" {
// Regenerate the macOS ad-hoc code signature.
cmd := testenv.Command(t, "codesign", "-s", "-", "-f", binPath)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("codesign failed: %v\n%s", err, out)
}
}
t.Logf("running modified binary...")
cmd := testenv.Command(t, binPath, "-test.v", "-test.run=^TestIntegrityCheck$")
cmd.Env = append(cmd.Environ(), "GODEBUG=fips140=on")
out, err := cmd.CombinedOutput()
t.Logf("%s", out)
if err == nil {
t.Errorf("modified binary did not fail as expected")
}
if !bytes.Contains(out, []byte("fips140: verification mismatch")) {
t.Errorf("modified binary did not fail with expected message")
}
if bytes.Contains(out, []byte("verified")) {
t.Errorf("modified binary did not exit")
}
}
func TestIntegrityCheckInfo(t *testing.T) {
cryptotest.MustSupportFIPS140(t)
// 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)
}
if p := checktest.PtrStaticData(); p != nil && *p != 10 {
t.Errorf("*checktest.PtrStaticData() = %d, want 10", *p)
}
// 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)))
if p := checktest.PtrStaticText(); p != nil {
sect(0, "StaticText", p)
}
sect(1, "RODATA", unsafe.Pointer(&checktest.RODATA))
sect(2, "NOPTRDATA", unsafe.Pointer(&checktest.NOPTRDATA))
if p := checktest.PtrStaticData(); p != nil {
sect(2, "StaticData", unsafe.Pointer(p))
}
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)
}
}