diff --git a/.github/workflows/client-v1-conformance.yml b/.github/workflows/client-v1-conformance.yml index 688a939b..20929040 100644 --- a/.github/workflows/client-v1-conformance.yml +++ b/.github/workflows/client-v1-conformance.yml @@ -9523,7 +9523,7 @@ jobs: ['scripts/phase1-linux-secret-service.sh', [5650, '83ce19c0dd6da5002f6853fa37addb4fc2d39f3d17beee1b1c39e1fce232b476']], ['scripts/unix-artifact-handoff.c', [18704, '2a003f9aa1d1886b9a593371a73cb65fe3a4a8b703f1c59fec8a27694367b7fc']], ['scripts/unix-producer-command.sh', [3223, 'ce9ec2ff00947f3ec0db53f144c99d34bc27de6085062d00dccff7c934c2e3c8']], - ['scripts/unix-producer-supervisor.sh', [29289, '1a83eb8495b671a9d490a26a8ce23745cdded680aa2b896142883acfde19064a']], + ['scripts/unix-producer-supervisor.sh', [29424, 'b73036415744c80ed27d5667f255ceea149096ca517b47c93a154299802206ff']], ]); for (const [path, [size, digest]] of expected) { const stats = lstatSync(path); diff --git a/docs/phase1-conformance.md b/docs/phase1-conformance.md index 9a344f08..731c8e30 100644 --- a/docs/phase1-conformance.md +++ b/docs/phase1-conformance.md @@ -1108,7 +1108,7 @@ The later SDK validator repin must use these exact committed file bytes: | File | Bytes | SHA-256 | | --- | ---: | --- | -| `.github/workflows/client-v1-conformance.yml` | 463,030 | `9d4deae1b25b5c2a6708fbeb760ad7e6af829ac18515672f746ed22e126406d9` | +| `.github/workflows/client-v1-conformance.yml` | 463,030 | `a08ad1025dd70096bd5a0cae5f3d37ff1675f0173de4347deebdddfdc90f225d` | | `scripts/contract-canary.mjs` | 39,636 | `140552c13e8eeba12396ce0e13f812b4e2c53cbcccd4c15a3365ed335827cfca` | | `scripts/executable-resolution.mjs` | 9,154 | `31e3c412ff8c835f14522f36a59e91f4a4ba82913210ae8e3b4455217503f430` | | `scripts/owned-temp-directory.mjs` | 6,965 | `a9c55c85cf2b7d70310d278bafd2c8e7695d66f4ae38b9c3f1f12fce0b442095` | @@ -1128,7 +1128,7 @@ The later SDK validator repin must use these exact committed file bytes: | `scripts/phase1-linux-secret-service.sh` | 5,650 | `83ce19c0dd6da5002f6853fa37addb4fc2d39f3d17beee1b1c39e1fce232b476` | | `scripts/unix-artifact-handoff.c` | 18,704 | `2a003f9aa1d1886b9a593371a73cb65fe3a4a8b703f1c59fec8a27694367b7fc` | | `scripts/unix-producer-command.sh` | 3,223 | `ce9ec2ff00947f3ec0db53f144c99d34bc27de6085062d00dccff7c934c2e3c8` | -| `scripts/unix-producer-supervisor.sh` | 29,289 | `1a83eb8495b671a9d490a26a8ce23745cdded680aa2b896142883acfde19064a` | +| `scripts/unix-producer-supervisor.sh` | 29,424 | `b73036415744c80ed27d5667f255ceea149096ca517b47c93a154299802206ff` | | `scripts/unix-producer-supervisor-attack.c` | 6,211 | `e485ebebb6570b06f179c03a3849224d59d96400b7cadd5547067cce35239642` | | `scripts/unix-producer-supervisor.test.sh` | 13,348 | `a8c6f48915b0c86a704a7ddc28eaa7f808ae0a3ddfcdb38c0c23ac0d83738f6d` | | `scripts/phase1-windows-supervisor-build.sh` | 4,646 | `713a9e0282887ade3e243b5ba175794d74cdb02c28c38dcd41491c9505812770` | @@ -1217,3 +1217,8 @@ are recomputed from the exact clean checkout and embedded in the platform record. The SDK aggregator still requires those values to equal the validator checkout performing aggregation. No SDK validator revision is committed back into Chat, so the two repositories do not form a commit-hash cycle. + +The Unix supervisor reports a nonzero restricted producer exit status on stderr +before containment drain, retaining it even when cleanup subsequently fails. +This numeric status complements the bounded producer stage diagnostic; it does +not identify the failed authority check or establish successful containment. diff --git a/phase1-conformance.lock.json b/phase1-conformance.lock.json index 9f27b6a9..67eba3f7 100644 --- a/phase1-conformance.lock.json +++ b/phase1-conformance.lock.json @@ -18,11 +18,11 @@ }, "harness": { "repository": "OpenCoven/chat", - "revision": "cd2f5a3ded0fe4dabf4cfc84ba53cdb293d2234a" + "revision": "4a700bce4217536b294fd2d2c58f579459685484" }, "harnessAuthority": { - "revision": "cd2f5a3ded0fe4dabf4cfc84ba53cdb293d2234a", - "tree": "d070889ca36ceb7f91c6181f748241516d891c1a", + "revision": "4a700bce4217536b294fd2d2c58f579459685484", + "tree": "898802820d9e35b2544b6f5705c401d13bda1700", "files": [ { "path": "scripts/phase1-conformance.mjs", @@ -111,8 +111,8 @@ }, { "path": "scripts/unix-producer-supervisor.sh", - "blob": "8d060a0ddc5dd27ef7378db63322e44169855dc9", - "sha256": "1a83eb8495b671a9d490a26a8ce23745cdded680aa2b896142883acfde19064a" + "blob": "0a27a5be4d2eb4981d35d7e3c178424c851697c5", + "sha256": "b73036415744c80ed27d5667f255ceea149096ca517b47c93a154299802206ff" }, { "path": "scripts/windows-job-supervisor.cs", @@ -146,8 +146,8 @@ }, { "path": ".github/workflows/client-v1-conformance.yml", - "blob": "688a939b241edb06471dde62ce5726ba3af3f813", - "sha256": "9d4deae1b25b5c2a6708fbeb760ad7e6af829ac18515672f746ed22e126406d9" + "blob": "20929040fbe91ff0f9b7f96f4b8acb16af167422", + "sha256": "a08ad1025dd70096bd5a0cae5f3d37ff1675f0173de4347deebdddfdc90f225d" } ], "productionDeltas": [ diff --git a/scripts/unix-producer-supervisor.sh b/scripts/unix-producer-supervisor.sh index 8d060a0d..0a27a5be 100755 --- a/scripts/unix-producer-supervisor.sh +++ b/scripts/unix-producer-supervisor.sh @@ -736,6 +736,10 @@ producer_status=$? set -e producer_pid= +if (( producer_status != 0 )); then + echo "unix-producer-supervisor: restricted producer exited with status $producer_status" >&2 +fi + if [[ "$host_os" == Linux ]]; then drain_linux_cgroup || { echo 'unix-producer-supervisor: cgroup.kill did not reach populated 0' >&2; exit 1; } diff --git a/src/phase1-conformance-lock.test.ts b/src/phase1-conformance-lock.test.ts index 394110ff..66f7f87f 100644 --- a/src/phase1-conformance-lock.test.ts +++ b/src/phase1-conformance-lock.test.ts @@ -80,8 +80,8 @@ const committedHarnessAuthority = JSON.parse( readFileSync(resolve(projectRoot, 'phase1-conformance.lock.json'), 'utf8'), ).harnessAuthority; const expectedBehaviorAuthority = { - revision: 'cd2f5a3ded0fe4dabf4cfc84ba53cdb293d2234a', - tree: 'd070889ca36ceb7f91c6181f748241516d891c1a', + revision: '4a700bce4217536b294fd2d2c58f579459685484', + tree: '898802820d9e35b2544b6f5705c401d13bda1700', files: [ { path: 'scripts/phase1-conformance.mjs', @@ -105,8 +105,8 @@ const expectedBehaviorAuthority = { }, { path: 'scripts/unix-producer-supervisor.sh', - blob: '8d060a0ddc5dd27ef7378db63322e44169855dc9', - sha256: '1a83eb8495b671a9d490a26a8ce23745cdded680aa2b896142883acfde19064a', + blob: '0a27a5be4d2eb4981d35d7e3c178424c851697c5', + sha256: 'b73036415744c80ed27d5667f255ceea149096ca517b47c93a154299802206ff', }, { path: 'scripts/windows-job-supervisor.cs', @@ -125,8 +125,8 @@ const expectedBehaviorAuthority = { }, { path: '.github/workflows/client-v1-conformance.yml', - blob: '688a939b241edb06471dde62ce5726ba3af3f813', - sha256: '9d4deae1b25b5c2a6708fbeb760ad7e6af829ac18515672f746ed22e126406d9', + blob: '20929040fbe91ff0f9b7f96f4b8acb16af167422', + sha256: 'a08ad1025dd70096bd5a0cae5f3d37ff1675f0173de4347deebdddfdc90f225d', }, ], } as const; diff --git a/src/unix-producer-exit-status.test.ts b/src/unix-producer-exit-status.test.ts new file mode 100644 index 00000000..a559059b --- /dev/null +++ b/src/unix-producer-exit-status.test.ts @@ -0,0 +1,70 @@ +import { spawnSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, test } from 'vitest'; + +const supervisor = readFileSync( + resolve(dirname(fileURLToPath(import.meta.url)), '../scripts/unix-producer-supervisor.sh'), + 'utf8', +); +const start = supervisor.indexOf('set +e\nwait "$producer_pid"'); +const end = supervisor.indexOf('\ndelete_producer_account ||', start); +const completion = supervisor.slice(start, end); + +describe.skipIf(process.platform === 'win32')('Unix producer completion diagnostics', () => { + test.each(['Linux', 'Darwin'])('%s reports child failure before draining containment', (host) => { + expect(start).toBeGreaterThanOrEqual(0); + expect(end).toBeGreaterThan(start); + const result = spawnSync( + 'bash', + [ + '-c', + `set -euo pipefail +host_os=$1 +producer_uid=40000 +cgroup_relative=test-cgroup +timed_out=0 +producer_contained=1 +containment_proved=0 +drain_linux_cgroup() { echo drained >&2; } +lock_macos_account() { :; } +drain_macos_uid() { echo drained >&2; } +(exit 23) & +producer_pid=$! +${completion}`, + 'test', + host, + ], + { encoding: 'utf8' }, + ); + expect(result.status).toBe(1); + expect(result.stderr).toBe( + 'unix-producer-supervisor: restricted producer exited with status 23\n' + + 'drained\n' + + 'unix-producer-supervisor: restricted production failed\n', + ); + }); + + test('does not report a failure for a successful child', () => { + const result = spawnSync( + 'bash', + [ + '-c', + `set -euo pipefail +host_os=Linux +cgroup_relative=test-cgroup +timed_out=0 +producer_contained=1 +containment_proved=0 +drain_linux_cgroup() { :; } +(exit 0) & +producer_pid=$! +${completion}`, + ], + { encoding: 'utf8' }, + ); + expect(result.status).toBe(0); + expect(result.stderr).toBe(''); + }); +});