mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
320 lines
11 KiB
TypeScript
320 lines
11 KiB
TypeScript
/** Lazy Koffi bindings for generic Win32 process, stdio, and Job operations. */
|
|
|
|
import koffi from 'koffi'
|
|
import * as abi from './abi.ts'
|
|
import { Win32Error } from './errors.ts'
|
|
|
|
declare const nativePtr: unique symbol
|
|
/** Koffi native pointer branded against accidental numeric use. */
|
|
export type NativePtr = bigint & { readonly [nativePtr]: true }
|
|
|
|
type Ptr = ReturnType<typeof koffi.pointer>
|
|
const PVOID: Ptr = koffi.pointer('void')
|
|
const PPVOID: Ptr = koffi.pointer(PVOID)
|
|
|
|
/** Loaded Win32 libraries and the shared stdcall binder used by process extensions. */
|
|
export interface Win32BindingContext {
|
|
/** Kernel process, handle, pipe, and Job APIs. */
|
|
readonly kernel32: ReturnType<typeof koffi.load>
|
|
/** Token and security APIs. */
|
|
readonly advapi32: ReturnType<typeof koffi.load>
|
|
/** Bind one stdcall function from a loaded Win32 library. */
|
|
readonly bind: (
|
|
library: ReturnType<typeof koffi.load>,
|
|
name: string,
|
|
result: Ptr | string,
|
|
args: Array<Ptr | string>,
|
|
) => unknown
|
|
}
|
|
|
|
/**
|
|
* Return whether a Koffi pointer represents NULL.
|
|
* @param value - pointer value returned by Koffi or a Win32 call.
|
|
* @returns true for null, undefined, or address zero.
|
|
*/
|
|
export function isNullPtr(value: NativePtr | null | undefined): value is null | undefined {
|
|
return value === null || value === undefined || (value as bigint) === 0n
|
|
}
|
|
|
|
/** STARTUPINFOW fields used by inherited or piped stdio launches. */
|
|
export interface StartupInfoInput {
|
|
cb: number
|
|
dwFlags: number
|
|
hStdInput: NativePtr
|
|
hStdOutput: NativePtr
|
|
hStdError: NativePtr
|
|
}
|
|
|
|
/** Decoded PROCESS_INFORMATION result. */
|
|
export interface ProcessInfoOutput {
|
|
hProcess: NativePtr | null
|
|
hThread: NativePtr | null
|
|
dwProcessId: number
|
|
dwThreadId: number
|
|
}
|
|
|
|
/** Generic Win32 calls consumed by restricted-token sandbox process operations. */
|
|
export interface Win32ProcessBindings {
|
|
closeHandle(handle: NativePtr): number
|
|
getLastError(): number
|
|
formatMessageW(
|
|
flags: number,
|
|
source: null,
|
|
messageId: number,
|
|
languageId: number,
|
|
buffer: Buffer,
|
|
size: number,
|
|
args: null,
|
|
): number
|
|
createPipe(readHandle: NativePtr, writeHandle: NativePtr, attributes: null, size: number): number
|
|
setHandleInformation(handle: NativePtr, mask: number, flags: number): number
|
|
createProcessAsUserW(
|
|
token: NativePtr,
|
|
applicationName: null,
|
|
commandLine: string,
|
|
processAttributes: null,
|
|
threadAttributes: null,
|
|
inheritHandles: number,
|
|
creationFlags: number,
|
|
environment: null,
|
|
currentDirectory: string | null,
|
|
startupInfo: NativePtr,
|
|
processInfo: NativePtr,
|
|
): number
|
|
readFile(file: NativePtr, buffer: Buffer, count: number, bytesRead: NativePtr, overlapped: null): number
|
|
peekNamedPipe(
|
|
pipe: NativePtr,
|
|
buffer: null,
|
|
size: number,
|
|
bytesRead: NativePtr | null,
|
|
totalAvail: NativePtr,
|
|
leftThisMessage: NativePtr | null,
|
|
): number
|
|
waitForSingleObject(handle: NativePtr, milliseconds: number): number
|
|
getExitCodeProcess(process: NativePtr, exitCode: NativePtr): number
|
|
createJobObjectW(attributes: null, name: null): NativePtr
|
|
setInformationJobObject(job: NativePtr, cls: number, information: Buffer, length: number): number
|
|
assignProcessToJobObject(job: NativePtr, process: NativePtr): number
|
|
resumeThread(thread: NativePtr): number
|
|
terminateProcess(process: NativePtr, exitCode: number): number
|
|
getStdHandle(stdHandle: number): NativePtr
|
|
}
|
|
|
|
/** Koffi STARTUPINFOW layout. */
|
|
export const STARTUPINFOW = koffi.struct('DSH_STARTUPINFOW', {
|
|
cb: 'uint32',
|
|
lpReserved: 'str16',
|
|
lpDesktop: 'str16',
|
|
lpTitle: 'str16',
|
|
dwX: 'uint32',
|
|
dwY: 'uint32',
|
|
dwXSize: 'uint32',
|
|
dwYSize: 'uint32',
|
|
dwXCountChars: 'uint32',
|
|
dwYCountChars: 'uint32',
|
|
dwFillAttribute: 'uint32',
|
|
dwFlags: 'uint32',
|
|
wShowWindow: 'uint16',
|
|
cbReserved2: 'uint16',
|
|
lpReserved2: koffi.pointer('uint8'),
|
|
hStdInput: PVOID,
|
|
hStdOutput: PVOID,
|
|
hStdError: PVOID,
|
|
})
|
|
|
|
/** Koffi PROCESS_INFORMATION layout. */
|
|
export const PROCESS_INFORMATION = koffi.struct('DSH_PROCESS_INFORMATION', {
|
|
hProcess: PVOID,
|
|
hThread: PVOID,
|
|
dwProcessId: 'uint32',
|
|
dwThreadId: 'uint32',
|
|
})
|
|
|
|
/* v8 ignore start -- ABI guards are pinned by native header probes. */
|
|
if (STARTUPINFOW.size !== abi.STARTUPINFOW_SIZE) {
|
|
throw new Error(`STARTUPINFOW layout mismatch: koffi computed ${STARTUPINFOW.size}, expected ${abi.STARTUPINFOW_SIZE}`)
|
|
}
|
|
if (PROCESS_INFORMATION.size !== abi.PROCESS_INFORMATION_SIZE) {
|
|
throw new Error(`PROCESS_INFORMATION layout mismatch: koffi computed ${PROCESS_INFORMATION.size}, expected ${abi.PROCESS_INFORMATION_SIZE}`)
|
|
}
|
|
/* v8 ignore stop */
|
|
|
|
/**
|
|
* Allocate a pointer-sized out-parameter slot.
|
|
* @returns allocated native slot.
|
|
*/
|
|
export function allocPtrSlot(): NativePtr {
|
|
return koffi.alloc(PVOID, 1) as NativePtr
|
|
}
|
|
|
|
/**
|
|
* Allocate a uint32 out-parameter slot.
|
|
* @returns allocated native slot.
|
|
*/
|
|
export function allocUint32(): NativePtr {
|
|
return koffi.alloc('uint32', 1) as NativePtr
|
|
}
|
|
|
|
/**
|
|
* Decode a pointer out-parameter.
|
|
* @param slot - pointer-sized slot filled by Win32.
|
|
* @returns decoded pointer, or null for address zero.
|
|
*/
|
|
export function decodePtr(slot: NativePtr): NativePtr | null {
|
|
const value = koffi.decode(slot, PVOID) as NativePtr | null
|
|
return isNullPtr(value) ? null : value
|
|
}
|
|
|
|
/**
|
|
* Decode a uint32 out-parameter.
|
|
* @param slot - uint32 slot filled by Win32.
|
|
* @returns decoded unsigned value.
|
|
*/
|
|
export function decodeUint32(slot: NativePtr): number {
|
|
return koffi.decode(slot, 'uint32') as number
|
|
}
|
|
|
|
/**
|
|
* Allocate a zeroed STARTUPINFOW.
|
|
* @returns allocated struct pointer.
|
|
*/
|
|
export function allocStartupInfo(): NativePtr {
|
|
return koffi.alloc(STARTUPINFOW, 1) as NativePtr
|
|
}
|
|
|
|
/**
|
|
* Encode the stdio-bearing STARTUPINFOW fields.
|
|
* @param startupInfo - allocated STARTUPINFOW pointer.
|
|
* @param fields - fields required for inherited stdio.
|
|
*/
|
|
export function encodeStartupInfo(startupInfo: NativePtr, fields: StartupInfoInput): void {
|
|
koffi.encode(startupInfo, STARTUPINFOW, fields)
|
|
}
|
|
|
|
/**
|
|
* Allocate a zeroed PROCESS_INFORMATION.
|
|
* @returns allocated struct pointer.
|
|
*/
|
|
export function allocProcessInfo(): NativePtr {
|
|
return koffi.alloc(PROCESS_INFORMATION, 1) as NativePtr
|
|
}
|
|
|
|
/**
|
|
* Decode PROCESS_INFORMATION.
|
|
* @param processInfo - struct pointer filled by CreateProcess.
|
|
* @returns process/thread handles and ids.
|
|
*/
|
|
export function decodeProcessInfo(processInfo: NativePtr): ProcessInfoOutput {
|
|
return koffi.decode(processInfo, PROCESS_INFORMATION) as ProcessInfoOutput
|
|
}
|
|
|
|
let cachedContext: Win32BindingContext | undefined
|
|
let cached: Win32ProcessBindings | undefined
|
|
|
|
/* v8 ignore start -- exercised by native Windows ABI and sandbox jobs. */
|
|
function bindingContext(): Win32BindingContext {
|
|
if (cachedContext !== undefined) return cachedContext
|
|
const kernel32 = koffi.load('kernel32.dll')
|
|
const advapi32 = koffi.load('advapi32.dll')
|
|
const bind = (
|
|
lib: ReturnType<typeof koffi.load>,
|
|
name: string,
|
|
result: Ptr | string,
|
|
args: Array<Ptr | string>,
|
|
): unknown => lib.func('__stdcall', name, result, args)
|
|
cachedContext = { kernel32, advapi32, bind }
|
|
return cachedContext
|
|
}
|
|
|
|
function bindings(): Win32ProcessBindings {
|
|
if (cached !== undefined) return cached
|
|
const { kernel32, advapi32, bind } = bindingContext()
|
|
cached = {
|
|
closeHandle: bind(kernel32, 'CloseHandle', 'int', [PVOID]),
|
|
getLastError: bind(kernel32, 'GetLastError', 'uint32', []),
|
|
formatMessageW: bind(kernel32, 'FormatMessageW', 'uint32', [
|
|
'uint32', PVOID, 'uint32', 'uint32', PVOID, 'uint32', PVOID,
|
|
]),
|
|
createPipe: bind(kernel32, 'CreatePipe', 'int', [PPVOID, PPVOID, PVOID, 'uint32']),
|
|
setHandleInformation: bind(kernel32, 'SetHandleInformation', 'int', [PVOID, 'uint32', 'uint32']),
|
|
createProcessAsUserW: bind(advapi32, 'CreateProcessAsUserW', 'int', [
|
|
PVOID, 'str16', 'str16', PVOID, PVOID, 'int', 'uint32', PVOID, 'str16',
|
|
koffi.pointer(STARTUPINFOW), koffi.pointer(PROCESS_INFORMATION),
|
|
]),
|
|
readFile: bind(kernel32, 'ReadFile', 'int', [PVOID, PVOID, 'uint32', koffi.pointer('uint32'), PVOID]),
|
|
peekNamedPipe: bind(kernel32, 'PeekNamedPipe', 'int', [
|
|
PVOID, PVOID, 'uint32', koffi.pointer('uint32'), koffi.pointer('uint32'), koffi.pointer('uint32'),
|
|
]),
|
|
waitForSingleObject: bind(kernel32, 'WaitForSingleObject', 'uint32', [PVOID, 'uint32']),
|
|
getExitCodeProcess: bind(kernel32, 'GetExitCodeProcess', 'int', [PVOID, koffi.pointer('uint32')]),
|
|
createJobObjectW: bind(kernel32, 'CreateJobObjectW', PVOID, [PVOID, 'str16']),
|
|
setInformationJobObject: bind(kernel32, 'SetInformationJobObject', 'int', [PVOID, 'int', PVOID, 'uint32']),
|
|
assignProcessToJobObject: bind(kernel32, 'AssignProcessToJobObject', 'int', [PVOID, PVOID]),
|
|
resumeThread: bind(kernel32, 'ResumeThread', 'uint32', [PVOID]),
|
|
terminateProcess: bind(kernel32, 'TerminateProcess', 'int', [PVOID, 'uint32']),
|
|
getStdHandle: bind(kernel32, 'GetStdHandle', PVOID, ['int']),
|
|
} as unknown as Win32ProcessBindings
|
|
return cached
|
|
}
|
|
|
|
/**
|
|
* Extend the shared process table with caller-owned Win32 API families.
|
|
* @param create - binds only the caller-specific operations from the shared libraries.
|
|
* @returns generic process bindings combined with the caller-specific operations.
|
|
*/
|
|
export function extendWin32ProcessBindings<Extension extends object>(
|
|
create: (context: Win32BindingContext) => Extension,
|
|
): Win32ProcessBindings & Extension {
|
|
return { ...bindings(), ...create(bindingContext()) }
|
|
}
|
|
/* v8 ignore stop */
|
|
|
|
/**
|
|
* Format a Win32 error code through FormatMessageW.
|
|
* @param api - active binding table.
|
|
* @param win32Code - captured GetLastError value.
|
|
* @returns trimmed system message, or an empty string when unavailable.
|
|
*/
|
|
export function errorText(api: Win32ProcessBindings, win32Code: number): string {
|
|
const buffer = Buffer.alloc(1024)
|
|
const length = api.formatMessageW(
|
|
abi.FORMAT_MESSAGE_FROM_SYSTEM | abi.FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
null,
|
|
win32Code,
|
|
0,
|
|
buffer,
|
|
buffer.length / 2,
|
|
null,
|
|
)
|
|
return length === 0 ? '' : buffer.subarray(0, length * 2).toString('utf16le').trim()
|
|
}
|
|
|
|
/**
|
|
* Throw the current GetLastError value.
|
|
* @param api - active binding table.
|
|
* @param name - failing Win32 operation.
|
|
* @param detail - optional operation context.
|
|
* @returns never; always throws Win32Error.
|
|
*/
|
|
export function throwLastError(api: Win32ProcessBindings, name: string, detail?: string): never {
|
|
const win32Code = api.getLastError()
|
|
throw new Win32Error(name, win32Code, detail ?? errorText(api, win32Code))
|
|
}
|
|
|
|
/**
|
|
* Throw an explicitly captured Win32 error code.
|
|
* @param api - active binding table.
|
|
* @param name - failing Win32 operation.
|
|
* @param win32Code - error captured before cleanup.
|
|
* @param detail - optional operation context.
|
|
* @returns never; always throws Win32Error.
|
|
*/
|
|
export function throwWin32(
|
|
api: Win32ProcessBindings,
|
|
name: string,
|
|
win32Code: number,
|
|
detail?: string,
|
|
): never {
|
|
throw new Win32Error(name, win32Code, detail ?? errorText(api, win32Code))
|
|
}
|