1
0
mirror of https://github.com/golang/go synced 2024-11-15 00:20:30 -07:00

crypto/internal/fips: add service indicator mechanism

Placed the fipsIndicator field in some 64-bit alignment padding in the g
struct to avoid growing per-goroutine memory requirements on 64-bit
targets.

Fixes #69911
Updates #69536

Change-Id: I176419d0e3814574758cb88a47340a944f405604
Reviewed-on: https://go-review.googlesource.com/c/go/+/620795
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Derek Parker <parkerderek86@gmail.com>
This commit is contained in:
Filippo Valsorda 2024-10-17 12:48:11 +02:00 committed by Gopher Robot
parent ba1caa8b30
commit f0b51a2099
10 changed files with 160 additions and 6 deletions

View File

@ -155,14 +155,13 @@ func setServiceIndicator(h fips.Hash, key []byte) {
// Per FIPS 140-3 IG C.M, key lengths below 112 bits are only allowed for
// legacy use (i.e. verification only) and we don't support that.
if len(key) < 112/8 {
return
fips.RecordNonApproved()
}
switch h.(type) {
case *sha256.Digest, *sha512.Digest, *sha3.Digest:
fips.RecordApproved()
default:
return
fips.RecordNonApproved()
}
// TODO(fips): set service indicator.
}

View File

@ -0,0 +1,57 @@
// 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 fips
import _ "unsafe" // for go:linkname
// The service indicator lets users of the module query whether invoked services
// are approved. Three states are stored in a per-goroutine value by the
// runtime. The indicator starts at indicatorUnset after a reset. Invoking an
// approved service transitions to indicatorTrue. Invoking a non-approved
// service transitions to indicatorFalse, and it can't leave that state until a
// reset. The idea is that functions can "delegate" checks to inner functions,
// and if there's anything non-approved in the stack, the final result is
// negative. Finally, we expose indicatorUnset as negative to the user, so that
// we don't need to explicitly annotate fully non-approved services.
//go:linkname getIndicator
func getIndicator() uint8
//go:linkname setIndicator
func setIndicator(uint8)
const (
indicatorUnset uint8 = iota
indicatorFalse
indicatorTrue
)
// ResetServiceIndicator clears the service indicator for the running goroutine.
func ResetServiceIndicator() {
setIndicator(indicatorUnset)
}
// ServiceIndicator returns true if and only if all services invoked by this
// goroutine since the last ResetServiceIndicator call are approved.
//
// If ResetServiceIndicator was not called before by this goroutine, its return
// value is undefined.
func ServiceIndicator() bool {
return getIndicator() == indicatorTrue
}
// RecordApproved is an internal function that records the use of an approved
// service. It does not override RecordNonApproved calls in the same span.
func RecordApproved() {
if getIndicator() == indicatorUnset {
setIndicator(indicatorTrue)
}
}
// RecordNonApproved is an internal function that records the use of a
// non-approved service. It overrides any RecordApproved calls in the same span.
func RecordNonApproved() {
setIndicator(indicatorFalse)
}

View File

@ -0,0 +1,76 @@
// 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 fips_test
import (
"crypto/internal/fips"
"testing"
)
func TestIndicator(t *testing.T) {
fips.ResetServiceIndicator()
if fips.ServiceIndicator() {
t.Error("indicator should be false if no calls are made")
}
fips.ResetServiceIndicator()
fips.RecordApproved()
if !fips.ServiceIndicator() {
t.Error("indicator should be true if RecordApproved is called")
}
fips.ResetServiceIndicator()
fips.RecordApproved()
fips.RecordApproved()
if !fips.ServiceIndicator() {
t.Error("indicator should be true if RecordApproved is called multiple times")
}
fips.ResetServiceIndicator()
fips.RecordNonApproved()
if fips.ServiceIndicator() {
t.Error("indicator should be false if RecordNonApproved is called")
}
fips.ResetServiceIndicator()
fips.RecordApproved()
fips.RecordNonApproved()
if fips.ServiceIndicator() {
t.Error("indicator should be false if both RecordApproved and RecordNonApproved are called")
}
fips.ResetServiceIndicator()
fips.RecordNonApproved()
fips.RecordApproved()
if fips.ServiceIndicator() {
t.Error("indicator should be false if both RecordNonApproved and RecordApproved are called")
}
fips.ResetServiceIndicator()
fips.RecordNonApproved()
done := make(chan struct{})
go func() {
fips.ResetServiceIndicator()
fips.RecordApproved()
close(done)
}()
<-done
if fips.ServiceIndicator() {
t.Error("indicator should be false if RecordApproved is called in a different goroutine")
}
fips.ResetServiceIndicator()
fips.RecordApproved()
done = make(chan struct{})
go func() {
fips.ResetServiceIndicator()
fips.RecordNonApproved()
close(done)
}()
<-done
if !fips.ServiceIndicator() {
t.Error("indicator should be true if RecordNonApproved is called in a different goroutine")
}
}

View File

@ -7,6 +7,7 @@
package sha256
import (
"crypto/internal/fips"
"errors"
"internal/byteorder"
)
@ -181,6 +182,7 @@ func (d *Digest) Write(p []byte) (nn int, err error) {
}
func (d *Digest) Sum(in []byte) []byte {
fips.RecordApproved()
// Make a copy of d so that caller can keep writing and summing.
d0 := *d
hash := d0.checkSum()

View File

@ -11,6 +11,7 @@
package sha3
import (
"crypto/internal/fips"
"crypto/internal/fips/subtle"
"errors"
)
@ -144,7 +145,11 @@ func (d *Digest) readGeneric(out []byte) (n int, err error) {
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
func (d *Digest) Sum(b []byte) []byte { return d.sum(b) }
func (d *Digest) Sum(b []byte) []byte {
fips.RecordApproved()
return d.sum(b)
}
func (d *Digest) sumGeneric(b []byte) []byte {
if d.state != spongeAbsorbing {
panic("sha3: Sum after Read")

View File

@ -6,6 +6,7 @@ package sha3
import (
"bytes"
"crypto/internal/fips"
"errors"
"internal/byteorder"
"math/bits"
@ -71,6 +72,7 @@ func (s *SHAKE) Sum(in []byte) []byte { return s.d.Sum(in) }
func (s *SHAKE) Write(p []byte) (n int, err error) { return s.d.Write(p) }
func (s *SHAKE) Read(out []byte) (n int, err error) {
fips.RecordApproved()
// Note that read is not exposed on Digest since SHA-3 does not offer
// variable output length. It is only used internally by Sum.
return s.d.read(out)

View File

@ -7,6 +7,7 @@
package sha512
import (
"crypto/internal/fips"
"errors"
"internal/byteorder"
)
@ -251,6 +252,7 @@ func (d *Digest) Write(p []byte) (nn int, err error) {
}
func (d *Digest) Sum(in []byte) []byte {
fips.RecordApproved()
// Make a copy of d so that caller can keep writing and summing.
d0 := new(Digest)
*d0 = *d

View File

@ -724,3 +724,13 @@ func reflect_addReflectOff(ptr unsafe.Pointer) int32 {
reflectOffsUnlock()
return id
}
//go:linkname fips_getIndicator crypto/internal/fips.getIndicator
func fips_getIndicator() uint8 {
return getg().fipsIndicator
}
//go:linkname fips_setIndicator crypto/internal/fips.setIndicator
func fips_setIndicator(indicator uint8) {
getg().fipsIndicator = indicator
}

View File

@ -466,6 +466,7 @@ type g struct {
trackingStamp int64 // timestamp of when the G last started being tracked
runnableTime int64 // the amount of time spent runnable, cleared when running, only used when tracking
lockedm muintptr
fipsIndicator uint8
sig uint32
writebuf []byte
sigcode0 uintptr

View File

@ -20,7 +20,7 @@ func TestSizeof(t *testing.T) {
_32bit uintptr // size on 32bit platforms
_64bit uintptr // size on 64bit platforms
}{
{runtime.G{}, 272, 432}, // g, but exported for testing
{runtime.G{}, 276, 432}, // g, but exported for testing
{runtime.Sudog{}, 56, 88}, // sudog, but exported for testing
}