mirror of
https://github.com/xroche/httrack.git
synced 2026-08-14 03:32:24 +03:00
Compare commits
4 Commits
master
...
zz-pidreus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d22b59809 | ||
|
|
65940365ee | ||
|
|
95c03624dd | ||
|
|
bfb9d4611d |
364
.github/workflows/zz-pidreuse-probe.yml
vendored
Normal file
364
.github/workflows/zz-pidreuse-probe.yml
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
# Scratch probe: does taskkill /F /T follow a recorded parent PID whose owner is
|
||||
# long dead? Not part of the build gate; branch-scoped, delete when answered.
|
||||
name: zz pid-reuse probe
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [zz-pidreuse-probe]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
probe:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Probe
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$sum = $env:GITHUB_STEP_SUMMARY
|
||||
|
||||
function Say([string]$m) {
|
||||
Write-Host $m
|
||||
if ($sum) { try { Add-Content -LiteralPath $sum -Value $m -ErrorAction SilentlyContinue } catch { } }
|
||||
}
|
||||
|
||||
Say "# PID-reuse probe on $env:COMPUTERNAME $(Get-Date -Format o)"
|
||||
Say ""
|
||||
Say '```'
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Part 1: is the hazard even present?
|
||||
# ---------------------------------------------------------------
|
||||
Say "== PART 1: recorded-parent state of the runner's own processes =="
|
||||
|
||||
$snap = @(Get-CimInstance Win32_Process)
|
||||
$byPid = @{}
|
||||
foreach ($p in $snap) { $byPid[[int]$p.ProcessId] = $p }
|
||||
Say ("snapshot: {0} live processes" -f $snap.Count)
|
||||
|
||||
function Stamp($d) { if ($null -eq $d) { return '?' } else { return ([datetime]$d).ToString('HH:mm:ss.fff') } }
|
||||
|
||||
function Classify([int]$q) {
|
||||
$c = $byPid[$q]
|
||||
if ($null -eq $c) { return 'NOPROC' }
|
||||
$par = $byPid[[int]$c.ParentProcessId]
|
||||
if ($null -eq $par) { return 'DEAD-PARENT' }
|
||||
if ($null -eq $par.CreationDate -or $null -eq $c.CreationDate) { return 'OK' }
|
||||
if ($par.CreationDate -gt $c.CreationDate) { return 'RECYCLED-PARENT' }
|
||||
return 'OK'
|
||||
}
|
||||
|
||||
function Chain([int]$q) {
|
||||
$out = @()
|
||||
$seen = @{}
|
||||
$cur = $q
|
||||
for ($i = 0; $i -lt 24; $i++) {
|
||||
$c = $byPid[$cur]
|
||||
if ($null -eq $c) { $out += ("{0}=<no such process>" -f $cur); break }
|
||||
$out += ("{0}/{1}@{2}" -f $c.ProcessId, $c.Name, (Stamp $c.CreationDate))
|
||||
if ($seen.ContainsKey($cur)) { $out += '<cycle>'; break }
|
||||
$seen[$cur] = $true
|
||||
$st = Classify $cur
|
||||
if ($st -ne 'OK') { $out += ("<<{0}>>" -f $st); break }
|
||||
$cur = [int]$c.ParentProcessId
|
||||
if ($cur -eq 0) { break }
|
||||
}
|
||||
return ($out -join ' <- ')
|
||||
}
|
||||
|
||||
$interesting = @($snap | Where-Object {
|
||||
$_.Name -like 'Runner.*' -or $_.Name -like 'Agent.*' -or $_.Name -like 'hosted-*' -or
|
||||
$_.Name -eq 'bash.exe' -or $_.Name -eq 'pwsh.exe' -or
|
||||
$_.Name -eq 'powershell.exe' -or $_.Name -eq 'dotnet.exe' -or
|
||||
$_.Name -eq 'node.exe' -or $_.Name -eq 'cmd.exe' -or
|
||||
$_.Name -eq 'conhost.exe' -or $_.Name -eq 'sh.exe'
|
||||
} | Sort-Object Name, ProcessId)
|
||||
|
||||
$armed = 0
|
||||
foreach ($p in $interesting) {
|
||||
$st = Classify ([int]$p.ProcessId)
|
||||
Say (" {0,-24} pid={1,-7} ppid={2,-7} {3,-16} {4}" -f $p.Name, $p.ProcessId, $p.ParentProcessId, $st, (Chain ([int]$p.ProcessId)))
|
||||
if ($st -ne 'OK' -and ($p.Name -like 'Runner.*' -or $p.Name -like 'Agent.*' -or $p.Name -like 'hosted-*')) { $armed++ }
|
||||
}
|
||||
|
||||
$dead = 0; $rec = 0; $ok = 0
|
||||
$boxOrphan = @{}
|
||||
foreach ($p in $snap) {
|
||||
$st = Classify ([int]$p.ProcessId)
|
||||
if ($st -eq 'DEAD-PARENT') {
|
||||
$dead++
|
||||
$k = [int]$p.ParentProcessId
|
||||
if (-not $boxOrphan.ContainsKey($k)) { $boxOrphan[$k] = @() }
|
||||
$boxOrphan[$k] += ("{0}/{1}" -f $p.ProcessId, $p.Name)
|
||||
} elseif ($st -eq 'RECYCLED-PARENT') { $rec++ } else { $ok++ }
|
||||
}
|
||||
Say ""
|
||||
Say ("box-wide: {0} processes, {1} with a DEAD recorded parent, {2} with a RECYCLED one, {3} sound" -f $snap.Count, $dead, $rec, $ok)
|
||||
foreach ($k in ($boxOrphan.Keys | Sort-Object)) {
|
||||
Say (" dead ppid {0,-7} still claimed as parent by: {1}" -f $k, ($boxOrphan[$k] -join ', '))
|
||||
}
|
||||
Say ""
|
||||
if ($armed -gt 0) {
|
||||
Say "PART 1 VERDICT: HAZARD PRESENT - $armed runner-agent process(es) record a parent PID that is dead or already recycled."
|
||||
} elseif ($dead -gt 0) {
|
||||
Say "PART 1 VERDICT: no Runner.*/Agent.* process has a stale recorded parent, but $dead other live processes do."
|
||||
} else {
|
||||
Say "PART 1 VERDICT: NOT ARMED - every live process's recorded parent is alive and older."
|
||||
}
|
||||
Say ""
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Part 2. Five sets of freed PIDs, released at the same instant, so
|
||||
# that a null on the set the theory needs can be told apart from a
|
||||
# probe that never recycles anything:
|
||||
# A plain ping, killed, .NET handle disposed (baseline: does reuse happen?)
|
||||
# C plain ping, killed, handle deliberately HELD (does a handle pin the PID?)
|
||||
# D cmd orphan-maker, child killed (is the construction sound?)
|
||||
# B cmd orphan-maker, ping child left ALIVE (the question)
|
||||
# E cmd orphan-maker, wscript child left ALIVE (same, with a child that
|
||||
# attaches to no console)
|
||||
# Every process inherits this shell's console, so no console host survives
|
||||
# to hold a handle on the dead cmd. Run 2 created 500 of those.
|
||||
# ---------------------------------------------------------------
|
||||
Say "== PART 2: can a dead parent's PID be handed out again, and does taskkill /T follow it? =="
|
||||
|
||||
$vbs = Join-Path $env:TEMP 'zzsleep.vbs'
|
||||
Set-Content -LiteralPath $vbs -Value 'WScript.Sleep 600000' -Encoding ascii
|
||||
|
||||
function New-Ping {
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = "$env:SystemRoot\System32\PING.EXE"
|
||||
$psi.Arguments = '-n 600 127.0.0.1'
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.CreateNoWindow = $false
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
return [System.Diagnostics.Process]::Start($psi)
|
||||
}
|
||||
|
||||
function New-Orphan([string]$childArgs) {
|
||||
# cmd exits at once, leaving the child alive with cmd's now-free PID recorded as parent.
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = "$env:ComSpec"
|
||||
$psi.Arguments = $childArgs
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.CreateNoWindow = $false
|
||||
return [System.Diagnostics.Process]::Start($psi)
|
||||
}
|
||||
|
||||
function Get-Proc([int]$q) {
|
||||
return (Get-CimInstance Win32_Process -Filter "ProcessId = $q" -ErrorAction SilentlyContinue | Select-Object -First 1)
|
||||
}
|
||||
|
||||
# --- control: a tree kill of a LIVE parent must kill its child, or the probe is blind.
|
||||
Say ""
|
||||
Say "-- control: taskkill /F /T on a live cmd whose child ping is genuine"
|
||||
$ctl = New-Orphan '/c ping -n 600 127.0.0.1 > NUL'
|
||||
Start-Sleep -Milliseconds 1500
|
||||
$ctlKid = @(Get-CimInstance Win32_Process -Filter "ParentProcessId = $($ctl.Id)")
|
||||
if ($ctlKid.Count -eq 0) {
|
||||
Say " control INCONCLUSIVE: no child found under cmd $($ctl.Id)"
|
||||
try { $ctl.Kill() } catch { }
|
||||
} else {
|
||||
$kp = [int]$ctlKid[0].ProcessId
|
||||
Say (" cmd {0} -> child {1}/{2}" -f $ctl.Id, $kp, $ctlKid[0].Name)
|
||||
$co = (& taskkill.exe /F /T /PID $ctl.Id 2>&1 | Out-String).Trim()
|
||||
Say (" taskkill: " + ($co -replace "`r?`n", ' | '))
|
||||
Start-Sleep -Milliseconds 800
|
||||
$still = Get-Proc $kp
|
||||
Say (" control result: child {0} {1}" -f $kp, $(if ($null -eq $still) { 'GONE - the probe can observe a tree kill' } else { 'STILL ALIVE - probe is blind, part 2 is inconclusive' }))
|
||||
}
|
||||
try { $ctl.Dispose() } catch { }
|
||||
|
||||
$conBefore = @(Get-Process conhost -ErrorAction SilentlyContinue).Count
|
||||
|
||||
# --- build the sets.
|
||||
$NB = 300; $ND = 200; $NE = 200; $NA = 200; $NC = 200
|
||||
Say ""
|
||||
Say "-- building sets: B=$NB (ping child alive) E=$NE (wscript child alive) D=$ND (child killed) A=$NA (baseline) C=$NC (handle held)"
|
||||
|
||||
$bCmd = New-Object 'System.Collections.Generic.List[System.Diagnostics.Process]'
|
||||
for ($i = 0; $i -lt $NB; $i++) { try { $bCmd.Add((New-Orphan '/c start /b ping -n 600 127.0.0.1 > NUL')) } catch { } }
|
||||
$eCmd = New-Object 'System.Collections.Generic.List[System.Diagnostics.Process]'
|
||||
for ($i = 0; $i -lt $NE; $i++) { try { $eCmd.Add((New-Orphan ('/c start /b wscript.exe //B //Nologo "' + $vbs + '"'))) } catch { } }
|
||||
$dCmd = New-Object 'System.Collections.Generic.List[System.Diagnostics.Process]'
|
||||
for ($i = 0; $i -lt $ND; $i++) { try { $dCmd.Add((New-Orphan '/c start /b ping -n 600 127.0.0.1 > NUL')) } catch { } }
|
||||
$aProc = New-Object 'System.Collections.Generic.List[System.Diagnostics.Process]'
|
||||
for ($i = 0; $i -lt $NA; $i++) { try { $aProc.Add((New-Ping)) } catch { } }
|
||||
$pinned = New-Object 'System.Collections.Generic.List[System.Diagnostics.Process]'
|
||||
for ($i = 0; $i -lt $NC; $i++) { try { $pinned.Add((New-Ping)) } catch { } }
|
||||
|
||||
$bPid = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
foreach ($p in $bCmd) { [void]$bPid.Add([int]$p.Id) }
|
||||
$ePid = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
foreach ($p in $eCmd) { [void]$ePid.Add([int]$p.Id) }
|
||||
$dPid = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
foreach ($p in $dCmd) { [void]$dPid.Add([int]$p.Id) }
|
||||
$aPid = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
foreach ($p in $aProc) { [void]$aPid.Add([int]$p.Id) }
|
||||
$cPid = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
foreach ($p in $pinned) { [void]$cPid.Add([int]$p.Id) }
|
||||
Say (" distinct PIDs held: B={0} E={1} D={2} A={3} C={4}" -f $bPid.Count, $ePid.Count, $dPid.Count, $aPid.Count, $cPid.Count)
|
||||
if ($bPid.Count -eq 0 -or $aPid.Count -eq 0) { Say " set construction failed, aborting"; Say '```'; exit 1 }
|
||||
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# Map the surviving children back to the cmd that made them.
|
||||
$orphan = @{}
|
||||
$dKids = New-Object 'System.Collections.Generic.List[int]'
|
||||
$nB = 0; $nE = 0
|
||||
foreach ($p in @(Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'ping.exe' -or $_.Name -eq 'wscript.exe' })) {
|
||||
$pp = [int]$p.ParentProcessId
|
||||
if ($bPid.Contains($pp) -or $ePid.Contains($pp)) {
|
||||
if (-not $orphan.ContainsKey($pp)) { $orphan[$pp] = @() }
|
||||
$orphan[$pp] += [pscustomobject]@{ Pid = [int]$p.ProcessId; Created = $p.CreationDate }
|
||||
if ($bPid.Contains($pp)) { $nB++ } else { $nE++ }
|
||||
} elseif ($dPid.Contains($pp)) {
|
||||
$dKids.Add([int]$p.ProcessId)
|
||||
}
|
||||
}
|
||||
Say (" surviving orphan children: {0} under B parents, {1} under E parents, {2} under D parents (to be killed)" -f $nB, $nE, $dKids.Count)
|
||||
|
||||
# Free every set at the same instant. Only C keeps its .NET handle open.
|
||||
foreach ($p in $bCmd) { try { [void]$p.WaitForExit(2000) } catch { }; try { $p.Dispose() } catch { } }
|
||||
foreach ($p in $eCmd) { try { [void]$p.WaitForExit(2000) } catch { }; try { $p.Dispose() } catch { } }
|
||||
foreach ($p in $dCmd) { try { [void]$p.WaitForExit(2000) } catch { }; try { $p.Dispose() } catch { } }
|
||||
foreach ($p in $aProc) { try { $p.Kill() } catch { } }
|
||||
foreach ($p in $pinned) { try { $p.Kill() } catch { } }
|
||||
Start-Sleep -Milliseconds 1000
|
||||
foreach ($p in $aProc) { try { $p.Dispose() } catch { } }
|
||||
$aProc.Clear(); $bCmd.Clear(); $eCmd.Clear(); $dCmd.Clear()
|
||||
if ($dKids.Count -gt 0) { Stop-Process -Id $dKids.ToArray() -Force -ErrorAction SilentlyContinue }
|
||||
[GC]::Collect(); [GC]::WaitForPendingFinalizers(); [GC]::Collect()
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
$liveNow = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
foreach ($p in @(Get-CimInstance Win32_Process)) { [void]$liveNow.Add([int]$p.ProcessId) }
|
||||
function CountLive($s) { $n = 0; foreach ($x in $s) { if ($liveNow.Contains($x)) { $n++ } }; return $n }
|
||||
Say (" set members still live (must be 0): B={0} E={1} D={2} A={3} C={4}" -f (CountLive $bPid), (CountLive $ePid), (CountLive $dPid), (CountLive $aPid), (CountLive $cPid))
|
||||
$conAfter = @(Get-Process conhost -ErrorAction SilentlyContinue).Count
|
||||
Say (" conhost.exe: {0} before, {1} after (a surviving console host would hold a handle on its dead creator and pin its PID)" -f $conBefore, $conAfter)
|
||||
$kidsAlive = 0
|
||||
foreach ($k in $orphan.Keys) { foreach ($o in $orphan[$k]) { if ($liveNow.Contains($o.Pid)) { $kidsAlive++ } } }
|
||||
Say (" orphan children still alive under a dead B/E parent: {0}" -f $kidsAlive)
|
||||
|
||||
# --- hunt.
|
||||
$hitA = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
$hitB = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
$hitC = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
$hitD = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
$hitE = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
$boxHits = 0
|
||||
$spawns = 0; $tested = 0; $followed = 0; $survived = 0
|
||||
$KILLCAP = 25
|
||||
$distinct = New-Object 'System.Collections.Generic.HashSet[int]'
|
||||
$window = New-Object 'System.Collections.Generic.Queue[System.Diagnostics.Process]'
|
||||
$minPid = [int]::MaxValue; $maxPid = 0
|
||||
|
||||
function Test-Hit([int]$np) {
|
||||
if ($script:aPid.Contains($np)) { [void]$script:hitA.Add($np) }
|
||||
if ($script:dPid.Contains($np)) { [void]$script:hitD.Add($np) }
|
||||
if ($script:cPid.Contains($np)) {
|
||||
if ($script:hitC.Add($np) -and $script:hitC.Count -le 5) { Say ("!! set C hit: PID $np was handed out although an open process handle on it is still held") }
|
||||
}
|
||||
if ($script:boxOrphan.ContainsKey($np)) {
|
||||
$script:boxHits++
|
||||
Say ""
|
||||
Say ("!! REAL COLLISION: our spawn got PID $np, which live box process(es) still record as their parent: $($script:boxOrphan[$np] -join ', ')")
|
||||
Say " (not tree-killing this one: it would reap a process we did not start)"
|
||||
Say ""
|
||||
}
|
||||
$isB = $script:bPid.Contains($np)
|
||||
$isE = $script:ePid.Contains($np)
|
||||
if (-not ($isB -or $isE)) { return $false }
|
||||
if ($isB) { [void]$script:hitB.Add($np) } else { [void]$script:hitE.Add($np) }
|
||||
if ($script:tested -ge $script:KILLCAP) { return $false }
|
||||
|
||||
$live = @()
|
||||
foreach ($o in $script:orphan[$np]) {
|
||||
$cur = Get-Proc $o.Pid
|
||||
if ($null -ne $cur -and $cur.CreationDate -eq $o.Created) { $live += $o }
|
||||
}
|
||||
if ($live.Count -eq 0) { return $false }
|
||||
|
||||
$script:tested++
|
||||
Say ""
|
||||
Say ("### HIT #$($script:tested) on set $(if ($isB) { 'B' } else { 'E' }): spawn #$($script:spawns) at t=$([int]$script:sw.Elapsed.TotalSeconds)s was handed PID $np, the PID of a dead parent whose child is still running")
|
||||
foreach ($k in @(Get-CimInstance Win32_Process -Filter "ParentProcessId = $np")) {
|
||||
Say (" the process table calls this a child of $np : {0}/{1} created {2}" -f $k.ProcessId, $k.Name, $k.CreationDate)
|
||||
}
|
||||
foreach ($o in $live) { Say (" stale-PPID orphan {0} is alive before the kill" -f $o.Pid) }
|
||||
Say (" issuing: taskkill /F /T /PID $np")
|
||||
$out = (& taskkill.exe /F /T /PID $np 2>&1 | Out-String).Trim()
|
||||
Say (" taskkill said: " + ($out -replace "`r?`n", ' | '))
|
||||
Start-Sleep -Milliseconds 1000
|
||||
$anyDied = $false
|
||||
foreach ($o in $live) {
|
||||
$cur = Get-Proc $o.Pid
|
||||
$alive = ($null -ne $cur -and $cur.CreationDate -eq $o.Created)
|
||||
if (-not $alive) { $anyDied = $true }
|
||||
Say (" stale-PPID orphan {0}: alive after the kill = {1}" -f $o.Pid, $alive)
|
||||
}
|
||||
if (-not $anyDied) { $script:survived++ }
|
||||
if ($anyDied) {
|
||||
$script:followed++
|
||||
Say " >>> THE TREE WALK FOLLOWED A STALE PPID. taskkill /T does not validate that the recorded parent is the real one."
|
||||
} else {
|
||||
Say " >>> the orphan survived: taskkill /T did not follow the stale PPID."
|
||||
}
|
||||
Say ""
|
||||
return $false
|
||||
}
|
||||
|
||||
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||
Say ""
|
||||
Say "-- hunting: rolling window of 200 live spawns, 300s cap"
|
||||
$done = $false
|
||||
while (-not $done -and $sw.Elapsed.TotalSeconds -lt 300) {
|
||||
try { $np = (New-Ping) } catch { Start-Sleep -Milliseconds 50; continue }
|
||||
$window.Enqueue($np)
|
||||
$spawns++
|
||||
$id = [int]$np.Id
|
||||
[void]$distinct.Add($id)
|
||||
if ($id -lt $minPid) { $minPid = $id }
|
||||
if ($id -gt $maxPid) { $maxPid = $id }
|
||||
try { $done = Test-Hit $id } catch { Say ("Test-Hit failed on $id : " + $_.Exception.Message) }
|
||||
while ($window.Count -gt 200) {
|
||||
$old = $window.Dequeue()
|
||||
try { $old.Kill() } catch { }
|
||||
try { $old.Dispose() } catch { }
|
||||
}
|
||||
if (($spawns % 250) -eq 0) { Write-Host (" ... spawn $spawns, distinct $($distinct.Count), A=$($hitA.Count) B=$($hitB.Count) C=$($hitC.Count) D=$($hitD.Count) E=$($hitE.Count), t=$([int]$sw.Elapsed.TotalSeconds)s") }
|
||||
}
|
||||
|
||||
Say ""
|
||||
Say "== RESULT =="
|
||||
Say ("spawns {0}, distinct PIDs {1}, range {2}..{3}, {4:N0}s" -f $spawns, $distinct.Count, $minPid, $maxPid, $sw.Elapsed.TotalSeconds)
|
||||
Say ("distinct set PIDs reissued, by set:")
|
||||
Say (" A killed, handle disposed, no child : {0} of {1}" -f $hitA.Count, $aPid.Count)
|
||||
Say (" C killed, handle still OPEN : {0} of {1}" -f $hitC.Count, $cPid.Count)
|
||||
Say (" D dead orphan-maker, child killed : {0} of {1}" -f $hitD.Count, $dPid.Count)
|
||||
Say (" B dead orphan-maker, ping child ALIVE : {0} of {1}" -f $hitB.Count, $bPid.Count)
|
||||
Say (" E dead orphan-maker, wscript ALIVE : {0} of {1}" -f $hitE.Count, $ePid.Count)
|
||||
Say ("tree kills issued on a reissued stale-parent PID: {0}; reached the orphan: {1}; orphan survived: {2}" -f $tested, $followed, $survived)
|
||||
Say ("collisions with a dead PPID recorded by a process we did NOT start: {0}" -f $boxHits)
|
||||
if ($tested -gt 0 -and $followed -eq 0) {
|
||||
Say "INTERPRETATION: a dead parent's PID IS reissued while a live process still records it as its parent, in $tested measured cases, and taskkill /F /T left that process alone every time. The tree walk does not follow a stale PPID: it skips a claimed child that predates the process now holding the PID."
|
||||
} elseif ($hitA.Count -eq 0 -and $hitB.Count -eq 0 -and $hitE.Count -eq 0) {
|
||||
Say "INTERPRETATION: nothing came back, so this run recycled nothing and says nothing either way."
|
||||
} elseif ($tested -eq 0) {
|
||||
Say "INTERPRETATION: PIDs recycle (A>0) but no PID with a live process recording it as parent ever came back. On this kernel such a PID stays reserved, so a tree kill cannot land on one and the stale-PPID mechanism cannot fire."
|
||||
} elseif ($followed -gt 0) {
|
||||
Say "INTERPRETATION: a dead parent's PID IS reissued while a live process records it, and taskkill /F /T reaped that process. The mechanism is real on this image."
|
||||
} else {
|
||||
Say "INTERPRETATION: the PID was reissued, but taskkill /F /T left the stale-PPID orphan alone: the tree walk validates parentage."
|
||||
}
|
||||
Say '```'
|
||||
|
||||
while ($window.Count -gt 0) { $o = $window.Dequeue(); try { $o.Kill() } catch { } }
|
||||
try { Get-Process ping, wscript -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue } catch { }
|
||||
Write-Host "probe done"
|
||||
Reference in New Issue
Block a user