mirror of
https://github.com/versity/versitygw.git
synced 2026-09-22 16:04:15 +00:00
rdma: pin session strings passed to the RC cgo ABI
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.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
//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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -371,18 +371,33 @@ func statusError(rc C.int) error {
|
||||
}
|
||||
}
|
||||
|
||||
// strIn borrows string bytes for a synchronous rc_str_in value argument.
|
||||
// For a string view embedded in a request struct passed by pointer, use
|
||||
// pinnedStrIn instead: the cgo pointer check rejects request structs that
|
||||
// contain unpinned Go string data. C must not retain or modify the borrowed
|
||||
// bytes.
|
||||
func strIn(s string) C.rc_str_in {
|
||||
if s == "" {
|
||||
return C.rc_str_in{ptr: nil, len: 0}
|
||||
}
|
||||
// The pointer must stay alive across the cgo call; every
|
||||
// wrapper keeps the string referenced until after the call.
|
||||
return C.rc_str_in{
|
||||
ptr: (*C.char)(unsafe.Pointer(unsafe.StringData(s))),
|
||||
len: C.uint32_t(len(s)),
|
||||
}
|
||||
}
|
||||
|
||||
// pinnedStrIn builds a strIn view and pins the string bytes for the
|
||||
// duration of the enclosing cgo call. The caller owns the Pinner and
|
||||
// must defer Unpin before the first pinnedStrIn call. Pinning an
|
||||
// interior pointer pins the whole backing allocation.
|
||||
func pinnedStrIn(s string, pins *runtime.Pinner) C.rc_str_in {
|
||||
in := strIn(s)
|
||||
if in.ptr != nil {
|
||||
pins.Pin(unsafe.Pointer(in.ptr))
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
// cStr reads a fixed C char array of length n into a Go string.
|
||||
func cStr(p *C.char, n C.uint32_t) string {
|
||||
if n == 0 {
|
||||
@@ -396,14 +411,13 @@ func (s *RCSvc) Prepare(req PrepareRequest) (*PrepareResponse, error) {
|
||||
if s.srv == nil {
|
||||
return nil, errors.New("rcserver: service closed")
|
||||
}
|
||||
var pins runtime.Pinner
|
||||
defer pins.Unpin()
|
||||
var principal C.rc_principal_id
|
||||
copy((*[32]byte)(unsafe.Pointer(&principal.id[0]))[:], req.Principal[:])
|
||||
|
||||
var token C.rc_str_in
|
||||
if req.ClientToken != "" {
|
||||
token = strIn(req.ClientToken)
|
||||
}
|
||||
target := strIn(req.Target)
|
||||
token := pinnedStrIn(req.ClientToken, &pins)
|
||||
target := pinnedStrIn(req.Target, &pins)
|
||||
creq := C.rc_prepare_req{
|
||||
principal: principal,
|
||||
op: C.uint8_t(req.Op),
|
||||
@@ -415,8 +429,6 @@ func (s *RCSvc) Prepare(req PrepareRequest) (*PrepareResponse, error) {
|
||||
client_token: token,
|
||||
}
|
||||
var cresp C.rc_prepare_resp
|
||||
runtime.KeepAlive(req.ClientToken)
|
||||
runtime.KeepAlive(req.Target)
|
||||
rc := C.rc_prepare(s.srv, &creq, &cresp)
|
||||
if rc != C.RC_OK {
|
||||
return nil, statusError(rc)
|
||||
@@ -510,9 +522,11 @@ func (s *RCSvc) ReadyTransfer(req ReadyRequest) (*ReadyResponse, error) {
|
||||
if s.srv == nil {
|
||||
return nil, errors.New("rcserver: service closed")
|
||||
}
|
||||
var pins runtime.Pinner
|
||||
defer pins.Unpin()
|
||||
var principal C.rc_principal_id
|
||||
copy((*[32]byte)(unsafe.Pointer(&principal.id[0]))[:], req.Principal[:])
|
||||
in := strIn(req.SessionID)
|
||||
in := pinnedStrIn(req.SessionID, &pins)
|
||||
creq := C.rc_ready_req{
|
||||
principal: principal,
|
||||
session_id: in,
|
||||
@@ -523,7 +537,6 @@ func (s *RCSvc) ReadyTransfer(req ReadyRequest) (*ReadyResponse, error) {
|
||||
}
|
||||
var cresp C.rc_ready_resp
|
||||
rc := C.rc_ready_transfer(s.srv, &creq, &cresp)
|
||||
runtime.KeepAlive(req.SessionID)
|
||||
if rc != C.RC_OK {
|
||||
return nil, statusError(rc)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user