/** * Restricted-token failure-path tests with stub binding tables (the * failure-paths.spec.ts pattern): every checked Win32 call in the token * pipeline — open, logon-SID scan, well-known SID creation, default-DACL * merge, restricted-token creation — has a failing counterpart, and each * failure closes or frees what it created before throwing. Pure stubs — no * real Win32 calls, so these run on every platform; the real-FFI round-trip * lives in acl.spec.ts (win32 only). */ import { describe, expect, it, vi } from 'vitest' import koffi from 'koffi' import { allocBytes, isNullPtr } from '../src/ffi.ts' import type { NativePtr, Win32Bindings } from '../src/ffi.ts' import { Win32Error } from '../src/errors.ts' import { createRestrictedToken, findLogonSid, makeWellKnownSid, openCurrentProcessToken, setTokenDefaultDaclGrant, } from '../src/token.ts' import * as abi from '../src/win32-abi.ts' const PVOID = koffi.pointer('void') describe('openCurrentProcessToken failure paths', () => { it('reports when OpenProcess yields no handle', () => { const api = { openProcess: vi.fn(() => 0n), getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings let caught: unknown try { openCurrentProcessToken(api) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('OpenProcess') }) it('closes the process handle and reports when OpenProcessToken fails', () => { const closeHandle = vi.fn(() => 1) const api = { openProcess: vi.fn(() => 7n), openProcessToken: vi.fn(() => 0), closeHandle, getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings let caught: unknown try { openCurrentProcessToken(api) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('OpenProcessToken') expect(closeHandle).toHaveBeenCalledWith(7n) }) it('reports a failed CloseHandle of the process handle', () => { const api = { openProcess: vi.fn(() => 7n), openProcessToken: vi.fn((_process: unknown, _access: unknown, slot: NativePtr) => { koffi.encode(slot, PVOID, 9n) return 1 }), closeHandle: vi.fn(() => 0), getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings let caught: unknown try { openCurrentProcessToken(api) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('CloseHandle') }) it('rejects a NULL token handle after a successful OpenProcessToken', () => { const api = { openProcess: vi.fn(() => 7n), openProcessToken: vi.fn(() => 1), // succeeds without writing the out slot closeHandle: vi.fn(() => 1), getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings let caught: unknown try { openCurrentProcessToken(api) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('OpenProcessToken') }) }) /** * The stub the logon-SID scan needs: the size probe writes `needed`, the * second call fills a TOKEN_GROUPS buffer (GroupCount@0, SID pointer@8, * attributes@16) with the state's one group. The CopySid mock comes back * beside the table for the one test that asserts on its arguments. */ function logonApi(state: { needed: number groupCount: number sidPtr: bigint logon: boolean secondOk?: boolean sidLength?: number copyOk?: boolean }): { api: Win32Bindings; copySid: ReturnType } { const copySid = vi.fn(() => (state.copyOk === false ? 0 : 1)) const api = { getTokenInformation: vi.fn((_token: unknown, cls: number, info: Buffer | null, _length: number, needed: NativePtr) => { if (cls !== abi.TokenGroups) throw new Error(`unexpected token information class ${cls}`) if (info === null) { koffi.encode(needed, 'uint32', state.needed) return 0 // the size probe is expected to "fail" } if (state.secondOk === false) return 0 info.writeUInt32LE(state.groupCount, 0) if (state.groupCount > 0) { info.writeBigUInt64LE(state.sidPtr, abi.TOKEN_GROUPS_OFFSET) info.writeUInt32LE(state.logon ? abi.SE_GROUP_LOGON_ID : 0, abi.TOKEN_GROUPS_OFFSET + 8) } return 1 }), getLengthSid: vi.fn(() => state.sidLength ?? 12), copySid, getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings return { api, copySid } } describe('findLogonSid failure paths', () => { const token = 9n as NativePtr it('reports a size probe that wrote nothing', () => { const { api } = logonApi({ needed: 0, groupCount: 0, sidPtr: 0n, logon: false }) let caught: unknown try { findLogonSid(api, token) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('GetTokenInformation') }) it('rejects an implausibly small TokenGroups size', () => { const { api } = logonApi({ needed: 4, groupCount: 0, sidPtr: 0n, logon: false }) let caught: unknown try { findLogonSid(api, token) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('GetTokenInformation') }) it('reports a failed TokenGroups read', () => { const { api } = logonApi({ needed: 24, groupCount: 1, sidPtr: 77n, logon: true, secondOk: false }) let caught: unknown try { findLogonSid(api, token) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('GetTokenInformation') }) it('skips a NULL group SID pointer and throws when no logon SID remains', () => { const { api } = logonApi({ needed: 24, groupCount: 1, sidPtr: 0n, logon: true }) expect(() => findLogonSid(api, token)).toThrow(/no logon SID found/u) }) it('skips a non-logon group and throws when no logon SID remains', () => { const { api } = logonApi({ needed: 24, groupCount: 1, sidPtr: 77n, logon: false }) expect(() => findLogonSid(api, token)).toThrow(/no logon SID found/u) }) it('reports a zero logon-SID length', () => { const { api } = logonApi({ needed: 24, groupCount: 1, sidPtr: 77n, logon: true, sidLength: 0 }) let caught: unknown try { findLogonSid(api, token) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('GetLengthSid') }) it('reports a failed CopySid of the logon SID', () => { const { api } = logonApi({ needed: 24, groupCount: 1, sidPtr: 77n, logon: true, copyOk: false }) let caught: unknown try { findLogonSid(api, token) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('CopySid') }) it('copies the logon SID and returns the new allocation', () => { const { api, copySid } = logonApi({ needed: 24, groupCount: 1, sidPtr: 77n, logon: true }) const copy = findLogonSid(api, token) expect(isNullPtr(copy)).toBe(false) expect(copySid).toHaveBeenCalledWith(12, copy, 77n) }) }) describe('makeWellKnownSid failure paths', () => { it('reports when CreateWellKnownSid fails', () => { const api = { createWellKnownSid: vi.fn(() => 0), getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings let caught: unknown try { makeWellKnownSid(api, abi.WinWorldSid) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('CreateWellKnownSid') }) it('reports when the created well-known SID is invalid', () => { const api = { createWellKnownSid: vi.fn(() => 1), isValidSid: vi.fn(() => 0), getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings let caught: unknown try { makeWellKnownSid(api, abi.WinWorldSid) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('IsValidSid') }) }) /** * The stub the default-DACL merge needs: the size probe writes `needed`, the * second call fills the DACL pointer slot, and the merge/apply calls follow * the state's results. */ function daclApi(state: { needed: number currentDacl: bigint secondOk?: boolean mergeResult?: number newDacl: bigint setTokenInfo?: number }): Win32Bindings { const api = { getTokenInformation: vi.fn((_token: unknown, cls: number, info: Buffer | null, _length: number, needed: NativePtr) => { if (cls !== abi.TokenDefaultDacl) throw new Error(`unexpected token information class ${cls}`) if (info === null) { koffi.encode(needed, 'uint32', state.needed) return 0 // the size probe is expected to "fail" } if (state.secondOk === false) return 0 info.writeBigUInt64LE(state.currentDacl, 0) return 1 }), setEntriesInAclW: vi.fn((_count: unknown, _entries: unknown, _old: unknown, newAcl: NativePtr) => { if (state.mergeResult !== undefined && state.mergeResult !== 0) return state.mergeResult koffi.encode(newAcl, PVOID, state.newDacl) return 0 }), setTokenInformation: vi.fn(() => state.setTokenInfo ?? 1), localFree: vi.fn(() => 0n), getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings return api } describe('setTokenDefaultDaclGrant failure paths', () => { const token = 9n as NativePtr const sid = 77n as NativePtr it('reports a size probe that wrote nothing', () => { const api = daclApi({ needed: 0, currentDacl: 0n, newDacl: 0n }) let caught: unknown try { setTokenDefaultDaclGrant(api, token, sid) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('GetTokenInformation') }) it('reports a failed default-DACL read', () => { const api = daclApi({ needed: 8, currentDacl: 88n, secondOk: false, newDacl: 0n }) let caught: unknown try { setTokenDefaultDaclGrant(api, token, sid) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('GetTokenInformation') }) it('rejects a token that carries no default DACL', () => { const api = daclApi({ needed: 8, currentDacl: 0n, newDacl: 0n }) expect(() => { setTokenDefaultDaclGrant(api, token, sid) }).toThrow(/no default DACL/u) }) it('reports a failed SetEntriesInAclW merge', () => { const api = daclApi({ needed: 8, currentDacl: 88n, mergeResult: 5, newDacl: 0n }) let caught: unknown try { setTokenDefaultDaclGrant(api, token, sid) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('SetEntriesInAclW') }) it('rejects a NULL merged default DACL', () => { const api = daclApi({ needed: 8, currentDacl: 88n, newDacl: 0n }) let caught: unknown try { setTokenDefaultDaclGrant(api, token, sid) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('SetEntriesInAclW') }) it('frees the merged DACL and reports when SetTokenInformation fails', () => { const localFree = vi.fn(() => 0n) const api = daclApi({ needed: 8, currentDacl: 88n, newDacl: 99n, setTokenInfo: 0 }) ;(api.localFree as unknown as ReturnType).mockImplementation(localFree) let caught: unknown try { setTokenDefaultDaclGrant(api, token, sid) } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('SetTokenInformation') expect(localFree).toHaveBeenCalledWith(99n) }) it('frees the merged DACL after a successful apply', () => { const localFree = vi.fn(() => 0n) const api = daclApi({ needed: 8, currentDacl: 88n, newDacl: 99n }) ;(api.localFree as unknown as ReturnType).mockImplementation(localFree) setTokenDefaultDaclGrant(api, token, sid) expect(localFree).toHaveBeenCalledWith(99n) }) }) describe('createRestrictedToken failure paths', () => { it('builds the read-only restricting list without a write SID', () => { const create = vi.fn(( _existing: unknown, _flags: unknown, _dc: unknown, _ds: unknown, _pc: unknown, _pd: unknown, count: number, _sids: unknown, slot: NativePtr, ) => { koffi.encode(slot, PVOID, 9n) expect(count).toBe(2) return 1 }) const api = { createRestrictedToken: create } as unknown as Win32Bindings const logon = allocBytes(12) expect(createRestrictedToken(api, 1n as NativePtr, logon, [], { world: 2n as NativePtr }, 'read-only')).toBe(9n) }) it('builds the workspace-write restricting list with the write SID', () => { const create = vi.fn(( _existing: unknown, _flags: unknown, _dc: unknown, _ds: unknown, _pc: unknown, _pd: unknown, count: number, _sids: unknown, slot: NativePtr, ) => { koffi.encode(slot, PVOID, 9n) expect(count).toBe(3) return 1 }) const api = { createRestrictedToken: create } as unknown as Win32Bindings const logon = allocBytes(12) expect(createRestrictedToken(api, 1n as NativePtr, logon, [3n as NativePtr], { world: 2n as NativePtr }, 'workspace-write')).toBe(9n) }) it('reports when CreateRestrictedToken fails', () => { const api = { createRestrictedToken: vi.fn(() => 0), getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings const logon = allocBytes(12) let caught: unknown try { createRestrictedToken(api, 1n as NativePtr, logon, [], { world: 2n as NativePtr }, 'read-only') } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('CreateRestrictedToken') }) it('rejects a NULL token handle after a successful CreateRestrictedToken', () => { const api = { createRestrictedToken: vi.fn(() => 1), // succeeds without writing the out slot getLastError: vi.fn(() => 5), formatMessageW: vi.fn(() => 0), } as unknown as Win32Bindings const logon = allocBytes(12) let caught: unknown try { createRestrictedToken(api, 1n as NativePtr, logon, [], { world: 2n as NativePtr }, 'read-only') } catch (error) { caught = error } expect(caught).toBeInstanceOf(Win32Error) expect((caught as Win32Error).api).toBe('CreateRestrictedToken') }) })