Skip to content

Commit 2bf2eb7

Browse files
committed
FEATURE-ci-freebsd-pid-zero: Accept the observed FreeBSD kernel process
Retain legitimate PID-zero snapshots with zero parent/group without relaxing malformed or duplicate row rejection. Keep owned positive process-group targeting and retirement bookkeeping unchanged. Verified parser and real descendant cleanup RED/GREEN, all 58 installed-consumer tests, final fixture portability check, Python syntax, and diff checks. Native FreeBSD proof remains CI-owned; the failing CI row was unlogged.
1 parent 48e4d02 commit 2bf2eb7

3 files changed

Lines changed: 110 additions & 1 deletion

File tree

‎scripts/lib/package_commands.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ def _process_snapshot(deadline):
267267
or any(flag not in '+<>ACEJLNSsVWX' for flag in fields[3][1:])):
268268
raise RuntimeError('malformed command process snapshot')
269269
pid, parent, group = map(int, fields[:3])
270-
if pid <= 0 or pid in members:
270+
# FreeBSD exposes kernel PID 0 in parent/group 0; it is observation only.
271+
if pid in members or (pid == 0 and (parent != 0 or group != 0)):
271272
raise RuntimeError('invalid command process snapshot PID')
272273
members[pid] = (parent, group, fields[3][0])
273274
if not members:

‎scripts/test-v3-installed-consumer.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,50 @@ def launch(argv, *args, **kwargs):
604604
return popen(argv, *args, **kwargs)
605605
return patch.object(subprocess, 'Popen', launch)
606606

607+
@unittest.skipUnless(os.name == 'posix', 'FreeBSD kernel process observation')
608+
def test_ps_kernel_pid_zero_preserves_owned_leader_and_live_descendant(self):
609+
data = '0 0 0 DLs\n2255 2178 2255 Z\n2258 2255 2255 S\n'
610+
with patch.object(sys, 'platform', 'freebsd14'), self.ps_output(data):
611+
members = commands._process_snapshot(time.monotonic() + 2)
612+
self.assertEqual({0: (0, 0, 'D'), 2255: (2178, 2255, 'Z'),
613+
2258: (2255, 2255, 'S')}, members)
614+
615+
@unittest.skipUnless(os.name == 'posix', 'FreeBSD kernel process validation')
616+
def test_ps_kernel_pid_zero_keeps_snapshot_validation_fail_closed(self):
617+
for data in ('', '0 0 0 DLs\n0 0 0 DLs', '0 1 0 DLs', '0 0 1 DLs',
618+
'-1 0 0 DLs', '0 -1 0 DLs', '0 0 -1 DLs',
619+
'0 0 0 !', '0 0 0 DQ', '0 0 DLs', '0 0 0 DLs command'):
620+
with self.subTest(data=data):
621+
with patch.object(sys, 'platform', 'freebsd14'), self.ps_output(data):
622+
with self.assertRaisesRegex(RuntimeError, 'snapshot'):
623+
commands._process_snapshot(time.monotonic() + 2)
624+
625+
@unittest.skipUnless(os.name == 'posix', 'FreeBSD kernel row during real group cleanup')
626+
def test_ps_kernel_pid_zero_does_not_prevent_owned_descendant_retirement(self):
627+
popen, killpg = subprocess.Popen, os.killpg
628+
groups = []
629+
def launch(argv, *args, **kwargs):
630+
if argv[0] in ('/bin/ps', '/usr/bin/ps'):
631+
code = ("import subprocess,sys; "
632+
f"data=subprocess.check_output({argv!r}); "
633+
"kernel=any(line.split()[0] == b'0' for line in data.splitlines()); "
634+
"sys.stdout.buffer.write((b'' if kernel else b'0 0 0 DLs\\n') + data)")
635+
argv = self.command(code)
636+
return popen(argv, *args, **kwargs)
637+
def signal_group(group, sig):
638+
self.assertGreater(group, 0, 'kernel PID/group zero must never be signalled')
639+
groups.append(group)
640+
return killpg(group, sig)
641+
with patch.object(sys, 'platform', 'freebsd14'), patch.object(os, 'waitid', None, create=True), \
642+
patch.object(subprocess, 'Popen', launch), patch.object(os, 'killpg', signal_group):
643+
self.assert_success_retires_socket_owner()
644+
self.assertTrue(groups, 'real owned group must be retired')
645+
record = json.loads((self.root / 'commands.json').read_text())[-1]
646+
data = Path(record['log']).read_bytes()
647+
self.assertEqual(0, record['exit'])
648+
self.assertEqual(hashlib.sha256(data).hexdigest(), record['sha256'])
649+
self.assertNotIn('error', record)
650+
607651
@unittest.skipUnless(os.name == 'posix', 'Darwin unknown scheduling state')
608652
def test_ps_unknown_state_preserves_complete_numeric_membership(self):
609653
# Captured native rows: the unknown process belongs to a different group.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# FEATURE-ci-freebsd-pid-zero: accept the FreeBSD kernel process snapshot
2+
3+
Status: Implemented; caller validation and finalization pending.
4+
Base: `v3` at `48e4d0208dd0d319e388571481263fb009878d28`.
5+
6+
FreeBSD package job `120396472458` in run `39948363870`, source
7+
`a68ba478e967988331c167af102189c905b6fe95`, fails during successful command
8+
cleanup with `invalid command process snapshot PID`, before the library
9+
matrix runs. That revision and the task base share the exact command module
10+
blob `e75901dededdf996ef70a4bfa723024825a38122`. The job did not log the
11+
rejected row, so attribution to kernel PID0 is corroborated rather than
12+
verified from a raw native row. The same a68 FreeBSD archive job
13+
`120398555704` / run `39948390044` has the same cleanup rejection after
14+
Node build completion; its raw log is retained alongside the package log.
15+
Rejecting legitimate PID0 is independently
16+
reproduced through the production parser.
17+
18+
The [FreeBSD Architecture Handbook](https://docs.freebsd.org/en/books/arch-handbook/boot/)
19+
identifies the scheduler/swapper as PID0. The
20+
[FreeBSD kernel initialization source](https://github.com/freebsd/freebsd-src/blob/releng/14.3/sys/kern/init_main.c)
21+
places proc0 in pgrp0. The parser now retains numeric PID0 with parent0/group0
22+
as observation data. Duplicates, malformed or negative fields, invalid states,
23+
and nonzero parent/group for PID0 still fail closed. Signal targets continue
24+
to come exclusively from the owned, unreaped, positive-PID session leader.
25+
26+
Action items:
27+
28+
- [x] Use the Groundwork identity helper and isolated worktree from the exact
29+
v3 base; preserve unrelated bauth work and closed investigations.
30+
- [x] Read process safety/TDD guidance and record concrete doubts before code.
31+
- [x] Reproduce rejection through actual `_process_snapshot` with kernel0,
32+
an owned leader, and a live descendant before editing production.
33+
- [x] Accept only the legitimate zero-parent/group kernel row; retain strict
34+
duplicate, malformed, negative, state and empty-snapshot controls.
35+
- [x] Exercise real command/descendant/socket cleanup and real ps with a
36+
kernel row, requiring positive signal targets and preserved output receipts.
37+
- [x] Preserve leader reservation/reaping, permission errors, exact group
38+
targeting, two-observation quiescence and shared cleanup deadlines.
39+
- [x] Run the affected installed-consumer module, Python syntax and diff checks.
40+
- [x] Save raw RED/GREEN evidence, source binding and journal in main
41+
`.debug/FEATURE-ci-freebsd-pid-zero/`.
42+
- [ ] Caller: Groundwork validation and local finalization into v3.
43+
- [ ] CI / v3 PR: native FreeBSD package execution and nonlocal checks.
44+
45+
Verification: three existing ps validation/ownership checks passed before
46+
edits. The new parser and real cleanup tests rejected the valid kernel row
47+
with the original exception in RED; after the minimal guard correction all
48+
three new tests pass, including 11 invalid/empty snapshot cases. All 58 tests
49+
in the affected installed-consumer module pass. Existing permission denial,
50+
ownership loss, unknown states, late descendants, deadlines and shell controls
51+
remain covered. Python AST syntax and `git diff --check` pass. CLAUDE.md's C++
52+
linter does not apply to these Python changes; it documents no Python linter.
53+
54+
These checks use controlled ps data or the local real ps with an added
55+
kernel row, and real local processes/sockets; they are not native FreeBSD
56+
receipts. Native checks remain incomplete and CI/v3-PR owned under AGENTS.
57+
No full native baseline, installation, workflow/runtime changes, validation
58+
loop, merge or publication was performed. Linux parity and Darwin deadline
59+
causality remain CLOSED/UNCERTAIN; further Darwin diagnostics still require
60+
the user's explicit approval.
61+
62+
Project notes: Python standard library tests need no installation. Full
63+
Autotools/C++20 builds are outside this script-only repair. Groundwork
64+
returned the exact task path/branch; Git metadata writes need escalation.

0 commit comments

Comments
 (0)