test(sandbox): close Windows ACL coverage gaps
This commit is contained in:
@@ -239,6 +239,8 @@ export class AclSandbox {
|
|||||||
this.tempWriteSidPtr = this.tempWriteSid === undefined ? undefined : parseSid(this.tempWriteSid)
|
this.tempWriteSidPtr = this.tempWriteSid === undefined ? undefined : parseSid(this.tempWriteSid)
|
||||||
|
|
||||||
const tempDir = this.mode === 'read-only' || this.tempDirOption === null ? null : this.tempDirOption
|
const tempDir = this.mode === 'read-only' || this.tempDirOption === null ? null : this.tempDirOption
|
||||||
|
/* v8 ignore next -- constructor validation requires workspace-write to supply
|
||||||
|
an explicit temp directory or null; the other branches normalize to null. */
|
||||||
if (tempDir === undefined) throw new Error('AclSandbox workspace-write temp directory was not resolved')
|
if (tempDir === undefined) throw new Error('AclSandbox workspace-write temp directory was not resolved')
|
||||||
if (tempDir !== null) {
|
if (tempDir !== null) {
|
||||||
if (!existsSync(tempDir) || !statSync(tempDir).isDirectory()) {
|
if (!existsSync(tempDir) || !statSync(tempDir).isDirectory()) {
|
||||||
|
|||||||
@@ -186,6 +186,28 @@ describe('AclSandbox constructor validation', () => {
|
|||||||
expect(sandbox.mode).toBe('read-only')
|
expect(sandbox.mode).toBe('read-only')
|
||||||
expect(sandbox.tempDir).toBeUndefined()
|
expect(sandbox.tempDir).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('rejects temp authority under read-only', () => {
|
||||||
|
const workspace = scratch()
|
||||||
|
const temp = scratch()
|
||||||
|
expect(() => new AclSandbox({ writableDirs: [workspace], tempDir: temp, mode: 'read-only' }))
|
||||||
|
.toThrow(/read-only does not accept a temp directory/u)
|
||||||
|
expect(() => new AclSandbox({ writableDirs: [workspace], tempDir: null, writeSid: 'S-1-4-9000-1', mode: 'read-only' }))
|
||||||
|
.toThrow(/read-only does not accept write SIDs/u)
|
||||||
|
expect(() => new AclSandbox({ writableDirs: [workspace], tempDir: null, tempWriteSid: 'S-1-4-9000-1-1', mode: 'read-only' }))
|
||||||
|
.toThrow(/read-only does not accept write SIDs/u)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects a temp SID when temp writes are disabled', () => {
|
||||||
|
const workspace = scratch()
|
||||||
|
expect(() => new AclSandbox({
|
||||||
|
writableDirs: [workspace],
|
||||||
|
tempDir: null,
|
||||||
|
writeSid: 'S-1-4-9000-2',
|
||||||
|
tempWriteSid: 'S-1-4-9000-2-1',
|
||||||
|
mode: 'workspace-write',
|
||||||
|
})).toThrow(/temp write SID requires a temp directory/u)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('AclSandbox init', () => {
|
describe('AclSandbox init', () => {
|
||||||
@@ -274,20 +296,29 @@ describe('AclSandbox init', () => {
|
|||||||
await expect(sandbox.init()).rejects.toBeInstanceOf(Win32Error)
|
await expect(sandbox.init()).rejects.toBeInstanceOf(Win32Error)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('reports a failed close of the current process token', async () => {
|
it('aggregates failed current and restricted token closes after init', async () => {
|
||||||
const { closeHandle } = state.stubs as HappyStubs
|
const { closeHandle, createRestrictedToken } = state.stubs as HappyStubs
|
||||||
const workspace = scratch()
|
const workspace = scratch()
|
||||||
const sandbox = new AclSandbox({ writableDirs: [workspace], tempDir: null, writeSid: 'S-1-4-9000-9', mode: 'workspace-write' })
|
const sandbox = new AclSandbox({ writableDirs: [workspace], tempDir: null, writeSid: 'S-1-4-9000-9', mode: 'workspace-write' })
|
||||||
|
const restrictedToken = 99n
|
||||||
|
createRestrictedToken.mockImplementation((
|
||||||
|
_existing: unknown, _flags: unknown, _dc: unknown, _ds: unknown, _pc: unknown, _pd: unknown,
|
||||||
|
_rc: unknown, _rs: unknown, slot: NativePtr,
|
||||||
|
) => {
|
||||||
|
koffi.encode(slot, PVOID, restrictedToken)
|
||||||
|
return 1
|
||||||
|
})
|
||||||
// fresh() hands out 1n to OpenProcess and 2n to OpenProcessToken; the
|
// fresh() hands out 1n to OpenProcess and 2n to OpenProcessToken; the
|
||||||
// token-layer close of 1n succeeds and init's close of 2n fails.
|
// token-layer close of 1n succeeds and init's close of 2n fails.
|
||||||
closeHandle.mockImplementation((handle: NativePtr) => (handle === 2n ? 0 : 1))
|
closeHandle.mockImplementation((handle: NativePtr) => (handle === 2n || handle === restrictedToken ? 0 : 1))
|
||||||
// The failure lands after this.token is stored but before this.api is
|
// The failure lands after this.token is stored but before this.api is
|
||||||
// assigned. Cleanup retries the still-open handle and reports both close
|
// assigned. Cleanup retries the still-open handle and reports both close
|
||||||
// failures after releasing the restricted token and parsed SIDs.
|
// failures plus the restricted-token close after releasing parsed SIDs.
|
||||||
await expect(sandbox.init()).rejects.toMatchObject({
|
await expect(sandbox.init()).rejects.toMatchObject({
|
||||||
errors: [
|
errors: [
|
||||||
{ api: 'CloseHandle' },
|
{ api: 'CloseHandle' },
|
||||||
{ api: 'CloseHandle' },
|
{ api: 'CloseHandle' },
|
||||||
|
{ api: 'CloseHandle' },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user