Source file src/crypto/internal/fips140test/check_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fipstest
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/internal/cryptotest"
    10  	. "crypto/internal/fips140/check"
    11  	"crypto/internal/fips140/check/checktest"
    12  	"fmt"
    13  	"internal/abi"
    14  	"internal/godebug"
    15  	"internal/testenv"
    16  	"os"
    17  	"os/exec"
    18  	"path/filepath"
    19  	"runtime"
    20  	"testing"
    21  	"unicode"
    22  	"unsafe"
    23  )
    24  
    25  func TestIntegrityCheck(t *testing.T) {
    26  	if Verified {
    27  		t.Logf("verified")
    28  		return
    29  	}
    30  
    31  	if godebug.New("fips140").Value() == "on" {
    32  		t.Fatalf("GODEBUG=fips140=on but verification did not run")
    33  	}
    34  
    35  	cryptotest.RerunWithFIPS140Enabled(t)
    36  }
    37  
    38  func TestIntegrityCheckFailure(t *testing.T) {
    39  	moduleStatus(t)
    40  	cryptotest.MustSupportFIPS140(t)
    41  
    42  	t.Logf("running modified binary...")
    43  	cmd := reexecCommand(t, "fips140=on", true, "-test.v", "-test.run=^TestIntegrityCheck$")
    44  	out, err := cmd.CombinedOutput()
    45  	t.Logf("running with GODEBUG=fips140=on:\n%s", out)
    46  	if err == nil {
    47  		t.Errorf("modified binary did not fail as expected")
    48  	}
    49  	if !bytes.Contains(out, []byte("fips140: verification mismatch")) {
    50  		t.Errorf("modified binary did not fail with expected message")
    51  	}
    52  	if bytes.Contains(out, []byte("verified")) {
    53  		t.Errorf("modified binary did not exit")
    54  	}
    55  }
    56  
    57  // browserBridge is the path to the browserbridge client binary, to be used to re-exec
    58  // self-tests. This lets the tests run on the host while exercising a js/wasm
    59  // module, which can't exec a subprocess. See crypto/internal/fips140test/_browserbridge.
    60  var browserBridge = os.Getenv("GOBROWSERBRIDGE")
    61  
    62  func reexecCommand(t *testing.T, godebug string, corrupt bool, args ...string) *exec.Cmd {
    63  	if browserBridge == "" {
    64  		exe := testenv.Executable(t)
    65  		if corrupt {
    66  			exe = corruptExecutable(t)
    67  		}
    68  		cmd := testenv.Command(t, exe, args...)
    69  		cmd.Env = append(cmd.Environ(), "GODEBUG="+godebug)
    70  		return cmd
    71  	}
    72  
    73  	bridgeArgs := []string{"-run"}
    74  	if corrupt {
    75  		bridgeArgs = append(bridgeArgs, "-corrupt")
    76  	}
    77  	bridgeArgs = append(append(bridgeArgs, "--"), args...)
    78  	cmd := testenv.Command(t, browserBridge, bridgeArgs...)
    79  	cmd.Env = append(cmd.Environ(), "GODEBUG="+godebug)
    80  	return cmd
    81  }
    82  
    83  func corruptExecutable(t *testing.T) string {
    84  	bin, err := os.ReadFile(testenv.Executable(t))
    85  	if err != nil {
    86  		t.Fatal(err)
    87  	}
    88  
    89  	// Replace the expected module checksum with a different value.
    90  	bin = bytes.ReplaceAll(bin, Linkinfo.Sum[:], bytes.Repeat([]byte("X"), len(Linkinfo.Sum)))
    91  
    92  	binPath := filepath.Join(t.TempDir(), "fips140test.exe")
    93  	if err := os.WriteFile(binPath, bin, 0o755); err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	if runtime.GOOS == "darwin" {
    98  		// Regenerate the macOS ad-hoc code signature.
    99  		cmd := testenv.Command(t, "codesign", "-s", "-", "-f", binPath)
   100  		out, err := cmd.CombinedOutput()
   101  		if err != nil {
   102  			t.Fatalf("codesign failed: %v\n%s", err, out)
   103  		}
   104  	}
   105  
   106  	return binPath
   107  }
   108  
   109  func TestIntegrityCheckInfo(t *testing.T) {
   110  	cryptotest.MustSupportFIPS140(t)
   111  
   112  	// Check that the checktest symbols are initialized properly.
   113  	if checktest.NOPTRDATA != 1 {
   114  		t.Errorf("checktest.NOPTRDATA = %d, want 1", checktest.NOPTRDATA)
   115  	}
   116  	if checktest.RODATA != 2 {
   117  		t.Errorf("checktest.RODATA = %d, want 2", checktest.RODATA)
   118  	}
   119  	if checktest.DATA.P != &checktest.NOPTRDATA {
   120  		t.Errorf("checktest.DATA.P = %p, want &checktest.NOPTRDATA (%p)", checktest.DATA.P, &checktest.NOPTRDATA)
   121  	}
   122  	if checktest.DATA.X != 3 {
   123  		t.Errorf("checktest.DATA.X = %d, want 3", checktest.DATA.X)
   124  	}
   125  	if checktest.NOPTRBSS != 0 {
   126  		t.Errorf("checktest.NOPTRBSS = %d, want 0", checktest.NOPTRBSS)
   127  	}
   128  	if checktest.BSS != nil {
   129  		t.Errorf("checktest.BSS = %p, want nil", checktest.BSS)
   130  	}
   131  	if p := checktest.PtrStaticData(); p != nil && *p != 10 {
   132  		t.Errorf("*checktest.PtrStaticData() = %d, want 10", *p)
   133  	}
   134  
   135  	// Check that the checktest symbols are in the right go:fipsinfo sections.
   136  	sect := func(i int, name string, p unsafe.Pointer) {
   137  		s := Linkinfo.Sects[i]
   138  		if !(uintptr(s.Start) <= uintptr(p) && uintptr(p) < uintptr(s.End)) {
   139  			t.Errorf("checktest.%s (%#x) not in section #%d (%#x..%#x)", name, p, i, s.Start, s.End)
   140  		}
   141  	}
   142  	sect(0, "TEXT", unsafe.Pointer(abi.FuncPCABIInternal(checktest.TEXT)))
   143  	if p := checktest.PtrStaticText(); p != nil {
   144  		sect(0, "StaticText", p)
   145  	}
   146  	sect(1, "RODATA", unsafe.Pointer(&checktest.RODATA))
   147  	sect(2, "NOPTRDATA", unsafe.Pointer(&checktest.NOPTRDATA))
   148  	if p := checktest.PtrStaticData(); p != nil {
   149  		sect(2, "StaticData", unsafe.Pointer(p))
   150  	}
   151  	sect(3, "DATA", unsafe.Pointer(&checktest.DATA))
   152  
   153  	// Check that some symbols are not in FIPS sections.
   154  	no := func(name string, p unsafe.Pointer, ix ...int) {
   155  		for _, i := range ix {
   156  			s := Linkinfo.Sects[i]
   157  			if uintptr(s.Start) <= uintptr(p) && uintptr(p) < uintptr(s.End) {
   158  				t.Errorf("%s (%#x) unexpectedly in section #%d (%#x..%#x)", name, p, i, s.Start, s.End)
   159  			}
   160  		}
   161  	}
   162  
   163  	// Check that the symbols are not in unexpected sections (that is, no overlaps).
   164  	no("checktest.TEXT", unsafe.Pointer(abi.FuncPCABIInternal(checktest.TEXT)), 1, 2, 3)
   165  	no("checktest.RODATA", unsafe.Pointer(&checktest.RODATA), 0, 2, 3)
   166  	no("checktest.NOPTRDATA", unsafe.Pointer(&checktest.NOPTRDATA), 0, 1, 3)
   167  	no("checktest.DATA", unsafe.Pointer(&checktest.DATA), 0, 1, 2)
   168  
   169  	// Check that non-FIPS symbols are not in any of the sections.
   170  	no("fmt.Printf", unsafe.Pointer(abi.FuncPCABIInternal(fmt.Printf)), 0, 1, 2, 3)     // TEXT
   171  	no("unicode.Categories", unsafe.Pointer(&unicode.Categories), 0, 1, 2, 3)           // BSS
   172  	no("unicode.ASCII_Hex_Digit", unsafe.Pointer(&unicode.ASCII_Hex_Digit), 0, 1, 2, 3) // DATA
   173  
   174  	// Check that we have enough data in total.
   175  	// On arm64 the fips sections in this test currently total 23 kB.
   176  	n := uintptr(0)
   177  	for _, s := range Linkinfo.Sects {
   178  		n += uintptr(s.End) - uintptr(s.Start)
   179  	}
   180  	if n < 16*1024 {
   181  		t.Fatalf("fips sections not big enough: %d, want at least 16 kB", n)
   182  	}
   183  }
   184  

View as plain text