Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion pkg/frost/signing/native_ffi_executor_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,20 @@ func (nefea *nativeExecutionFFIExecutorAdapter) Execute(
ctx context.Context,
logger log.StandardLogger,
request *Request,
) (*Result, error) {
) (result *Result, err error) {
// Recover any panic originating along the cgo/FFI signing path (e.g. a
// malformed or oversized response from the native signer) and surface it as
// a failed attempt instead of crashing the whole signing process. The outer
// tBTC signingRetryLoop then handles this attempt cleanly.
defer func() {
if r := recover(); r != nil {
result = nil
err = fmt.Errorf(
"native FFI signing panicked at the cgo boundary: %v", r,
)
}
}()

if request == nil {
return nil, fmt.Errorf("request is nil")
}
Expand Down
41 changes: 41 additions & 0 deletions pkg/frost/signing/native_ffi_executor_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,47 @@ func (mnefsp *mockNativeExecutionFFISigningPrimitive) RegisterUnmarshallers(
mnefsp.lastChannel = channel
}

// panickingFFISigningPrimitive panics in Sign, standing in for a panic raised
// along the cgo/FFI path (e.g. a C.GoBytes length-out-of-range on a malformed
// native-signer response).
type panickingFFISigningPrimitive struct{}

func (panickingFFISigningPrimitive) Sign(
ctx context.Context,
logger log.StandardLogger,
request *NativeExecutionFFISigningRequest,
) (*frost.Signature, error) {
panic("simulated cgo boundary panic")
}

func (panickingFFISigningPrimitive) RegisterUnmarshallers(channel net.BroadcastChannel) {}

func TestNativeExecutionFFIExecutorAdapter_Execute_RecoversCgoBoundaryPanic(
t *testing.T,
) {
executor, err := NewNativeExecutionFFIExecutorAdapter(panickingFFISigningPrimitive{})
if err != nil {
t.Fatalf("unexpected adapter setup error: [%v]", err)
}

result, err := executor.Execute(context.Background(), nil, &Request{
Message: big.NewInt(1),
SignerMaterial: []byte{0x01},
})
if err == nil {
t.Fatal("expected an error from the recovered panic, got nil")
}
if result != nil {
t.Fatalf("expected a nil result on a recovered panic, got [%v]", result)
}
if !strings.Contains(err.Error(), "panicked at the cgo boundary") {
t.Fatalf(
"expected a cgo-boundary panic error, got: [%v]",
err,
)
}
}

func TestNewNativeExecutionFFIExecutorAdapter_NilPrimitive(t *testing.T) {
_, err := NewNativeExecutionFFIExecutorAdapter(nil)
if err == nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"math"
"unsafe"
)

Expand Down Expand Up @@ -2636,7 +2637,15 @@ func callBuildTaggedTBTCSignerOperation(
}

requestPtr := C.CBytes(requestPayload)
defer C.free(requestPtr)
requestLen := len(requestPayload)
defer func() {
// Scrub the secret request bytes from the C heap before releasing them.
// The request payload can carry signing-share / nonce material, and a
// plain C.free does not overwrite; this mirrors the Go-side zeroBytes
// hygiene applied to the caller's own copy.
zeroBytes(unsafe.Slice((*byte)(requestPtr), requestLen))
C.free(requestPtr)
}()

result := call((*C.uint8_t)(requestPtr), C.size_t(len(requestPayload)))
return parseBuildTaggedTBTCSignerResult(operation, result)
Expand All @@ -2659,6 +2668,20 @@ func parseBuildTaggedTBTCSignerResult(

var payload []byte
if result.buffer.ptr != nil && result.buffer.len > 0 {
// Guard the size_t -> C.int narrowing in C.GoBytes: a length that does
// not fit in a C.int (>= 2^31) would overflow to a negative value and
// panic ("length out of range") at the cgo boundary, or silently
// truncate to a wrong length. A response that large is never valid, so
// reject it; the buffer is still released by the deferred free above.
if uint64(result.buffer.len) > uint64(math.MaxInt32) {
return nil, buildTaggedTBTCSignerOperationError(
operation,
fmt.Sprintf(
"response buffer length [%d] exceeds maximum [%d]",
uint64(result.buffer.len), math.MaxInt32,
),
)
}
payload = C.GoBytes(unsafe.Pointer(result.buffer.ptr), C.int(result.buffer.len))
}

Expand Down
Loading