Bound the Windows clipboard read by the block size instead of scanning for a NUL

This commit is contained in:
Viktor Liu
2026-09-23 12:37:05 +02:00
parent 8f158a0827
commit e42fe6dfc0
2 changed files with 39 additions and 1 deletions
+20 -1
View File
@@ -4,6 +4,7 @@ package server
import (
"runtime"
"slices"
"sync"
"unsafe"
@@ -559,6 +560,7 @@ var (
procGlobalAlloc = kernel32.NewProc("GlobalAlloc")
procGlobalLock = kernel32.NewProc("GlobalLock")
procGlobalSize = kernel32.NewProc("GlobalSize")
procGlobalUnlock = kernel32.NewProc("GlobalUnlock")
procGlobalFree = kernel32.NewProc("GlobalFree")
)
@@ -673,13 +675,30 @@ func (w *WindowsInputInjector) GetClipboard() string {
return ""
}
// Bounded by the block's own size. Whoever set the clipboard decides
// what is in it, including whether it ends in a NUL, and this runs as
// SYSTEM: scanning for a terminator that is not there reads past the
// allocation into the agent's own memory and hands that to the viewer.
size, _, _ := procGlobalSize.Call(hData)
if size < 2 {
return ""
}
ptr, _, _ := procGlobalLock.Call(hData)
if ptr == 0 {
return ""
}
defer logCleanupCallArgs("GlobalUnlock", procGlobalUnlock, hData)
return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
return utf16UpToNUL(unsafe.Slice((*uint16)(unsafe.Pointer(ptr)), size/2))
}
// utf16UpToNUL decodes units up to the first NUL, or all of them when there
// is none.
func utf16UpToNUL(units []uint16) string {
if i := slices.Index(units, 0); i >= 0 {
units = units[:i]
}
return windows.UTF16ToString(units)
}
var _ InputInjector = (*WindowsInputInjector)(nil)
@@ -0,0 +1,19 @@
//go:build windows
package server
import (
"testing"
"github.com/stretchr/testify/assert"
)
// Clipboard text is set by whichever local process owns the clipboard, and
// nothing makes it end in a NUL. Decoding must stop at the block's end, not
// run on looking for a terminator.
func TestUTF16UpToNUL(t *testing.T) {
assert.Equal(t, "hi", utf16UpToNUL([]uint16{'h', 'i', 0, 'x'}), "stops at the first NUL")
assert.Equal(t, "hi", utf16UpToNUL([]uint16{'h', 'i'}), "an unterminated block decodes to its end and no further")
assert.Equal(t, "", utf16UpToNUL([]uint16{0, 'x'}), "a leading NUL is an empty string")
assert.Equal(t, "", utf16UpToNUL(nil))
}