fix(gateway): enforce audited production traversal

This commit is contained in:
sechmachine
2026-07-30 04:48:02 +07:00
parent d3852d15f3
commit 70667d5aee
17 changed files with 1368 additions and 317 deletions
+267 -22
View File
@@ -14,6 +14,7 @@ import (
"math/big"
"net"
"os"
"reflect"
"strings"
"sync"
"sync/atomic"
@@ -97,6 +98,13 @@ func TestClientFeedbackUsesFixedProtocolVGFVector(t *testing.T) {
if _, err := DecodeClientFeedback([]byte{'F', 'B', 'R', 'K', 0}); !errors.Is(err, ErrProviderMalformed) {
t.Fatalf("legacy feedback accepted: %v", err)
}
disconnected, err := EncodeProviderEvent(ProviderEvent{Kind: ProviderEventDisconnected})
if err != nil || hex.EncodeToString(disconnected) != "5647463101130000" {
t.Fatalf("disconnected event vector = %x, %v", disconnected, err)
}
if event, err := DecodeProviderEvent(disconnected); err != nil || event.Kind != ProviderEventDisconnected {
t.Fatalf("decoded disconnected event = %#v, %v", event, err)
}
}
func TestCapabilityIntersectionAndBoundedQueue(t *testing.T) {
@@ -124,6 +132,24 @@ func TestCapabilityIntersectionAndBoundedQueue(t *testing.T) {
}
}
func TestNewServerRejectsPartiallyConfiguredCapabilities(t *testing.T) {
serverTLS, _ := testTLS(t)
fake := NewFakeApollo(FakeApolloConfig{Now: time.Now()})
server, err := NewServer(ServerConfig{
TLSConfig: serverTLS, GatewayID: "gateway-1",
Capabilities: protocol.CapabilityProfile{SourceRateControl: "server"},
ProviderCapabilities: DefaultCapabilities(),
Admission: &oneTimeAdmission{},
Provider: fake,
})
if server != nil {
_ = server.Close()
}
if err == nil {
t.Fatal("partial capability configuration was silently replaced with defaults")
}
}
func TestSyntheticImpairmentPacingAndResourceBounds(t *testing.T) {
payload := make([]byte, 1179*16+1)
if _, err := FragmentPayload(ChannelVideo, 1, 0, payload); !errors.Is(err, ErrFrameFragmentedLimit) {
@@ -187,10 +213,10 @@ func TestApolloFixturesAndLifecycle(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if got := <-session.Video(); string(got) != string(video) {
if got := <-session.Video(); string(got.Payload) != string(video) {
t.Fatalf("video changed: %x", got)
}
if got := <-session.Audio(); string(got) != string(audio) {
if got := <-session.Audio(); string(got.Payload) != string(audio) {
t.Fatalf("audio changed: %x", got)
}
if err := session.Input(context.Background(), InputEvent{Sequence: 1, Device: "keyboard", Code: 7, Pressed: true}); err != nil {
@@ -321,11 +347,96 @@ func TestAdmissionQUICMTLSRelayAndCleanup(t *testing.T) {
}
}
func TestGatewayTelemetrySeparatesQueueProcessingAndPacing(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
session := &fakeSession{
video: make(chan ProviderMedia, 1),
audio: make(chan ProviderMedia),
events: make(chan ProviderEvent, 1),
clipboardWrites: make(chan string, 1),
state: protocol.ProviderState{
Version: "1", SessionID: "session-timing", State: ProviderStateStarting,
Channels: []string{"video", "audio", "input", "feedback"},
},
pressed: make(map[string]struct{}),
}
provider := providerStartFunc(func(context.Context, LaunchRequest) (ProviderSession, error) {
enqueuedAt := time.Now()
session.video <- ProviderMedia{Payload: bytesRepeat(0x5a, 2000), ReceivedAt: enqueuedAt, EnqueuedAt: enqueuedAt}
time.Sleep(60 * time.Millisecond)
session.mu.Lock()
session.state.State = ProviderStateReady
session.mu.Unlock()
return session, nil
})
authority := protocol.SessionAuthority{
Version: "1", SessionID: "session-timing", GatewayID: "gateway-1", Audience: "versevdi-gateway",
ExpiresAt: time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339Nano),
Capabilities: DefaultCapabilities(), ProviderProfile: ProviderProfileApollo,
ProviderIdentity: "apollo-fixture-1#sha256:fixture-apollo-1",
}
admission := &oneTimeAdmission{authority: authority, released: make(chan struct{}), disableClipboard: true}
server, err := NewServer(ServerConfig{
ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: authority.GatewayID,
Capabilities: DefaultCapabilities(), ProviderCapabilities: DefaultCapabilities(),
Admission: admission, Provider: provider, PacerKbps: 24,
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
serveDone := make(chan error, 1)
go func() { serveDone <- server.Serve(ctx) }()
request := protocol.TunnelAdmissionRequest{
Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID, Audience: authority.Audience,
Grant: strings.Repeat("g", 64), ClientNonce: "nonce-0000000001", DeviceSignature: strings.Repeat("s", 86),
Capabilities: DefaultCapabilities(),
}
client, err := Dial(context.Background(), server.Addr().String(), clientTLS, request)
if err != nil {
t.Fatal(err)
}
receiveCtx, receiveCancel := context.WithTimeout(context.Background(), 2*time.Second)
for index := byte(0); index < 2; index++ {
frame, err := client.ReceiveFrame(receiveCtx)
if err != nil {
t.Fatal(err)
}
if frame.FragmentIndex != index || frame.FragmentCount != 2 {
t.Fatalf("timing frame = %#v", frame)
}
}
receiveCancel()
metrics := server.Metrics()
if metrics.ProcessingSamples != 1 {
t.Fatalf("timing samples = %d, want one provider unit", metrics.ProcessingSamples)
}
if metrics.MediaPackets != 2 {
t.Fatalf("media packets = %d, want two fragments", metrics.MediaPackets)
}
queue, processing, pacing := time.Duration(metrics.QueueDelayNanos), time.Duration(metrics.ProcessingDelayNanos), time.Duration(metrics.PacingDelayNanos)
if queue < 40*time.Millisecond || queue > 150*time.Millisecond {
t.Fatalf("queue residence = %s, want the controlled 60ms provider queue wait", queue)
}
if processing >= 100*time.Millisecond {
t.Fatalf("processing = %s, pacing leaked into gateway processing", processing)
}
if pacing < 500*time.Millisecond {
t.Fatalf("pacing = %s, want the controlled scheduler wait", pacing)
}
_ = client.Close()
cancel()
_ = server.Close()
if err := <-serveDone; err != nil {
t.Fatal(err)
}
}
func TestGatewayRejectsProviderWorkOutsideNegotiatedDecodeProfile(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
fake := NewFakeApollo(FakeApolloConfig{Now: time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)})
capabilities := DefaultCapabilities()
capabilities.ClientDecode = "h264-opus"
capabilities.ClientDecode = []string{"h264-opus"}
authority := protocol.SessionAuthority{
Version: "1", SessionID: "session-policy", GatewayID: "gateway-1", Audience: "versevdi-gateway",
ExpiresAt: time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339Nano),
@@ -371,6 +482,75 @@ func TestGatewayRejectsProviderWorkOutsideNegotiatedDecodeProfile(t *testing.T)
}
}
func TestGatewayNegotiatesRegisteredProfilesWithIndependentClient(t *testing.T) {
for _, test := range []struct {
name string
clientProfiles []string
selected string
codec string
}{
{name: "h264-only", clientProfiles: []string{"h264-opus"}, selected: "h264-opus", codec: "H264"},
{name: "hevc-only", clientProfiles: []string{"hevc-opus"}, selected: "hevc-opus", codec: "HEVC"},
{name: "policy-selects-hevc", clientProfiles: []string{"h264-opus", "hevc-opus"}, selected: "hevc-opus", codec: "HEVC"},
} {
t.Run(test.name, func(t *testing.T) {
serverTLS, clientTLS := testTLS(t)
fake := NewFakeApollo(FakeApolloConfig{Now: time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)})
clientCapabilities := DefaultCapabilities()
clientCapabilities.ClientDecode = test.clientProfiles
authority := protocol.SessionAuthority{
Version: "1", SessionID: "session-profile-" + test.name, GatewayID: "gateway-1", Audience: "versevdi-gateway",
ExpiresAt: time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339Nano),
Capabilities: clientCapabilities, ProviderProfile: ProviderProfileApollo, ProviderIdentity: fake.config.Identity.Key(),
}
admission := &oneTimeAdmission{
authority: authority, released: make(chan struct{}), disableClipboard: true,
streamPolicy: protocol.ProviderStreamPolicy{
ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60,
Codec: test.codec, BitrateKbps: 8000, AudioEnabled: true,
},
}
started := make(chan LaunchRequest, 1)
provider := providerStartFunc(func(ctx context.Context, request LaunchRequest) (ProviderSession, error) {
started <- request
return fake.Start(ctx, request)
})
server, err := NewServer(ServerConfig{
ListenAddress: "127.0.0.1:0", TLSConfig: serverTLS, GatewayID: authority.GatewayID,
Capabilities: DefaultCapabilities(), ProviderCapabilities: DefaultCapabilities(),
Admission: admission, Provider: provider,
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
serveDone := make(chan error, 1)
go func() { serveDone <- server.Serve(ctx) }()
request := protocol.TunnelAdmissionRequest{
Version: "1", SessionID: authority.SessionID, GatewayID: authority.GatewayID, Audience: authority.Audience,
Grant: strings.Repeat("g", 64), ClientNonce: "nonce-0000000001", DeviceSignature: strings.Repeat("s", 86),
Capabilities: clientCapabilities,
}
client, err := Dial(context.Background(), server.Addr().String(), clientTLS, request)
if err != nil {
cancel()
_ = server.Close()
t.Fatal(err)
}
launch := <-started
if !reflect.DeepEqual(launch.Capabilities.ClientDecode, []string{test.selected}) {
t.Fatalf("provider selected profiles = %v", launch.Capabilities.ClientDecode)
}
_ = client.Close()
cancel()
_ = server.Close()
if err := <-serveDone; err != nil {
t.Fatal(err)
}
})
}
}
func TestRegisteredChannelFramesTraversePublicTransport(t *testing.T) {
h := newGatewayTransportHarness(t)
@@ -525,9 +705,60 @@ func TestProviderTerminationEndsPublicGatewaySession(t *testing.T) {
}
func TestEncryptedNativeHostTerminationEndsPublicGatewaySession(t *testing.T) {
h := newNativeGatewayLifecycleHarness(t, "session-native-terminal")
h.native.handleApolloControlPayload(apolloChannelGeneric, true, sourceSealHostControl(t, h.key, 0, apolloControlTypeTerm, []byte{1, 2, 3, 4}))
tryQueueNativeMedia(h.native, h.native.video, []byte("queued-video"))
tryQueueNativeMedia(h.native, h.native.audio, []byte("queued-audio"))
eventCtx, eventCancel := context.WithTimeout(context.Background(), time.Second)
event, err := h.client.ReceiveProviderEvent(eventCtx)
eventCancel()
if err != nil || event.Kind != ProviderEventTerminated {
t.Fatalf("native provider termination = %#v, %v", event, err)
}
tryQueueNativeMedia(h.native, h.native.video, []byte("new-video"))
tryQueueNativeMedia(h.native, h.native.audio, []byte("new-audio"))
h.assertNoMedia(t)
h.waitReleased(t)
if states := h.reporter.States(); len(states) == 0 || states[len(states)-1].State != ProviderStateTerminated {
t.Fatalf("provider states = %#v", states)
}
}
func TestNativeENetDisconnectQuiescesPublicGatewaySession(t *testing.T) {
h := newNativeGatewayLifecycleHarness(t, "session-native-disconnect")
h.native.handleApolloDisconnect(ErrProviderDisconnected)
tryQueueNativeMedia(h.native, h.native.video, []byte("queued-video"))
tryQueueNativeMedia(h.native, h.native.audio, []byte("queued-audio"))
eventCtx, eventCancel := context.WithTimeout(context.Background(), time.Second)
event, err := h.client.ReceiveProviderEvent(eventCtx)
eventCancel()
if err != nil || event.Kind != ProviderEventDisconnected {
t.Fatalf("native provider disconnect = %#v, %v", event, err)
}
tryQueueNativeMedia(h.native, h.native.video, []byte("new-video"))
tryQueueNativeMedia(h.native, h.native.audio, []byte("new-audio"))
h.assertNoMedia(t)
h.waitReleased(t)
if states := h.reporter.States(); len(states) == 0 || states[len(states)-1].State != ProviderStateDisconnected || states[len(states)-1].CleanupPending {
t.Fatalf("provider states = %#v", states)
}
}
type nativeGatewayLifecycleHarness struct {
native *nativeApolloSession
key []byte
client *Client
admission *oneTimeAdmission
reporter *recordingProviderStateReporter
}
func newNativeGatewayLifecycleHarness(t *testing.T, sessionID string) nativeGatewayLifecycleHarness {
t.Helper()
serverTLS, clientTLS := testTLS(t)
key := []byte("0123456789abcdef")
native := newNativeApolloSession("session-native-terminal")
native := newNativeApolloSession(sessionID)
control, err := newApolloControlCodec(key)
if err != nil {
t.Fatal(err)
@@ -557,7 +788,6 @@ func TestEncryptedNativeHostTerminationEndsPublicGatewaySession(t *testing.T) {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
serveDone := make(chan error, 1)
go func() { serveDone <- server.Serve(ctx) }()
request := protocol.TunnelAdmissionRequest{
@@ -569,28 +799,39 @@ func TestEncryptedNativeHostTerminationEndsPublicGatewaySession(t *testing.T) {
if err != nil {
t.Fatal(err)
}
native.handleApolloControlPayload(apolloChannelGeneric, true, sourceSealHostControl(t, key, 0, apolloControlTypeTerm, []byte{1, 2, 3, 4}))
eventCtx, eventCancel := context.WithTimeout(context.Background(), time.Second)
event, err := client.ReceiveProviderEvent(eventCtx)
eventCancel()
if err != nil || event.Kind != ProviderEventTerminated {
t.Fatalf("native provider termination = %#v, %v", event, err)
}
t.Cleanup(func() {
_ = client.Close()
cancel()
_ = server.Close()
if err := <-serveDone; err != nil {
t.Errorf("serve: %v", err)
}
})
return nativeGatewayLifecycleHarness{native: native, key: key, client: client, admission: admission, reporter: reporter}
}
func (h nativeGatewayLifecycleHarness) waitReleased(t *testing.T) {
t.Helper()
select {
case <-admission.released:
case <-h.admission.released:
case <-time.After(2 * time.Second):
t.Fatal("native termination did not release admission")
t.Fatal("native terminal state did not release admission")
}
if states := reporter.States(); len(states) == 0 || states[len(states)-1].State != ProviderStateTerminated {
t.Fatalf("provider states = %#v", states)
}
_ = client.Close()
_ = server.Close()
if err := <-serveDone; err != nil {
t.Fatal(err)
}
func (h nativeGatewayLifecycleHarness) assertNoMedia(t *testing.T) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond)
defer cancel()
if frame, err := h.client.ReceiveFrame(ctx); err == nil {
t.Fatalf("media crossed after native terminal signal: channel=%d payload=%q", frame.Channel, frame.Payload)
}
}
func tryQueueNativeMedia(session *nativeApolloSession, channel chan ProviderMedia, payload []byte) {
session.enqueueMedia(channel, payload, time.Now())
}
func TestProviderDisconnectEndsPublicGatewaySessionReconnectable(t *testing.T) {
h := newGatewayTransportHarnessWithoutClipboard(t)
h.drainInitialMedia(t)
@@ -752,6 +993,7 @@ type oneTimeAdmission struct {
releases atomic.Int64
released chan struct{}
streamPolicy protocol.ProviderStreamPolicy
providerWork *protocol.ProviderSessionWork
disableClipboard bool
}
@@ -795,9 +1037,12 @@ func (a *oneTimeAdmission) Admit(context.Context, protocol.TunnelAdmissionReques
}
func (a *oneTimeAdmission) ProviderWork(_ context.Context, authority protocol.SessionAuthority) (protocol.ProviderSessionWork, error) {
if authority != a.authority {
if !reflect.DeepEqual(authority, a.authority) {
return protocol.ProviderSessionWork{}, ErrAdmissionRejected
}
if a.providerWork != nil {
return *a.providerWork, nil
}
streamPolicy := a.streamPolicy
if streamPolicy == (protocol.ProviderStreamPolicy{}) {
streamPolicy = protocol.ProviderStreamPolicy{ResolutionWidth: 1920, ResolutionHeight: 1080, Fps: 60, Codec: "H264", BitrateKbps: 8000, AudioEnabled: true}