mirror of
https://github.com/versity/versitygw.git
synced 2026-09-24 08:54:47 +00:00
The Prepare and ReadyTransfer wrappers embed string views built from Go heap strings inside request structs passed to C by pointer. The cgo pointer check rejects such requests when the string data is an unpinned Go heap pointer, so any live PREPARE or READY call with header-derived strings panicked at the call boundary and the route returned a 500. Constant strings passed the check because their data lives in read-only static storage, which is why standalone callers kept working while the gateway did not. Pin the string bytes with runtime.Pinner for the duration of the cgo call and drop the now redundant KeepAlive calls in those two wrappers. The other string-taking wrappers pass rc_str_in by value and are unaffected. Also add a deviceless cgo boundary regression test that calls the real Prepare wrapper with heap-backed interior-pointer strings and an invalid opcode, so C returns from argument validation before the server handle is touched.
59 lines
1.9 KiB
Go
59 lines
1.9 KiB
Go
//go:build linux && cgo
|
|
|
|
package rcserver
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
// heapSubstring returns a substring whose backing array is heap-allocated
|
|
// and whose data pointer is an interior pointer, mirroring how header
|
|
// strings reach the binding from the gateway routes.
|
|
func heapSubstring(value string) string {
|
|
const prefix = "prefix:"
|
|
backing := strings.Clone(prefix + value + ":suffix")
|
|
return backing[len(prefix) : len(prefix)+len(value)]
|
|
}
|
|
|
|
// TestPrepareHeapStringsReachCValidation calls the real Prepare wrapper
|
|
// with heap-backed strings. The request uses an invalid opcode so the C
|
|
// entrypoint returns RC_E_ARG from its argument validation before the
|
|
// server handle is dereferenced; the dummy handle below is never touched.
|
|
// Before the pinnedStrIn fix this call panics at the cgo pointer check;
|
|
// after it, the C argument validation runs and the error surfaces.
|
|
func TestPrepareHeapStringsReachCValidation(t *testing.T) {
|
|
const literalTarget = "/bucket1/obj1"
|
|
heapTarget := heapSubstring(literalTarget)
|
|
heapToken := heapSubstring(strings.Repeat("0", 88))
|
|
for _, tc := range []struct {
|
|
name, target, token string
|
|
}{
|
|
{"literal_control", literalTarget, ""},
|
|
{"heap_target", heapTarget, ""},
|
|
{"heap_token", literalTarget, heapToken},
|
|
{"both_heap", heapTarget, heapToken},
|
|
{"empty_control", "", ""},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var svc RCSvc
|
|
// Test-only opaque sentinel, not an initialized rc_server.
|
|
// Op=255 returns in C argument validation before the
|
|
// server pointer is dereferenced.
|
|
dummy := new(uint64)
|
|
*(*unsafe.Pointer)(unsafe.Pointer(&svc.srv)) = unsafe.Pointer(dummy)
|
|
resp, err := svc.Prepare(PrepareRequest{
|
|
Op: 255,
|
|
Size: 1,
|
|
Target: tc.target,
|
|
ClientToken: tc.token,
|
|
})
|
|
if resp != nil || !errors.Is(err, ErrArg) {
|
|
t.Fatalf("Prepare = (%v, %v), want (nil, ErrArg)", resp, err)
|
|
}
|
|
})
|
|
}
|
|
}
|