Skip to content

Commit 86202e8

Browse files
committed
inverted run loop: confirm the mechanism on hardware
Fresh core on the CI-matching kernel. filter=-16, flags=EV_EOF|EV_ONESHOT, udata==x0 pointing at a freed mach_kev_reg whose word 2 (0x16) matches the MACH_DEBUG_WRAP ident from the same run. Fault is ldrb [NULL,#9] = dux_type(du)->dst_action. Every predicted value matched; -22 ruled out. Also: crash rate 13/30 vs 9/30 across the kernel boundary - #168 changed nothing measurable.
1 parent 31d9918 commit 86202e8

1 file changed

Lines changed: 33 additions & 2 deletions

File tree

‎nextbsd-inverted-runloop.html‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,41 @@ <h3>The fix</h3>
284284

285285
<p><strong>Under 50 lines, in one file, with no kernel change and no libdispatch change</strong> &mdash; which preserves the property that this tree builds unmodified Apple libdispatch.</p>
286286

287-
<div class="callout callout-warn">
288-
<p><strong>One value would settle the last inference.</strong> The chain above is verified in source, but that <em>this particular core&rsquo;s</em> faulting kevent is that event is inferred. The <code>filter</code> field of <code>_dispatch_kevent_merge</code>&rsquo;s second argument decides it: <strong>&minus;16</strong> confirms the raw-native leak above; <strong>&minus;22</strong> would mean a synthesized event carrying a stale <code>udata</code> &mdash; a different bug needing a different fix. Cheaper than a debugger: <code>MACH_DEBUG_WRAP=1 scrltest</code> prints every synthesized delivery, and on a crashing run the fatal event will have <em>no</em> matching <code>[WRAP] deliver</code> line.</p>
287+
<div class="callout callout-good">
288+
<p><strong>Confirmed on hardware, 2026-09-01.</strong> The chain above was verified in source; the remaining inference was whether <em>this</em> crash is that chain. A fresh core on the CI-matching kernel settles it &mdash; <strong>every value predicted in advance matched.</strong></p>
289289
</div>
290290

291+
<p>The faulting kevent, decoding <code>struct kevent_qos_s</code> (ident 0, filter 8, flags 10, qos 12, udata 16):</p>
292+
293+
<pre><code>0x89692550: 0x0000000000000017 ident = 23
294+
0x000000008010fff0 filter = 0xfff0 = -16 ← EVFILT_MACHPORT_NATIVE
295+
flags = 0x8010 ← EV_EOF | EV_ONESHOT
296+
0x89692560: 0x000063efa4e01000 udata = 0x63efa4e01000 ← identical to x0</code></pre>
297+
298+
<p><strong>Filter &minus;16</strong> is the raw-native leak; <strong>&minus;22</strong> would have meant the sibling bug. <strong><code>EV_EOF | EV_ONESHOT</code></strong> is the exact signature <code>knlist_clear()</code> stamps on the knote in <code>ipc_pset_destroy()</code>. And the object libdispatch dereferenced:</p>
299+
300+
<pre><code>0x63efa4e01000: 0x0000000000000000 "du_type" → NULL (freed list pointer)
301+
0x0000000000000007 "du_owner_wref" → 7 (an fd)
302+
0x63efa4e01010: 0x0000000000000016 → 22
303+
0x0000000000000017 → 23</code></pre>
304+
305+
<p>Two port names in a struct libdispatch believes is a dispatch unote. The cross-check that closes it: <strong><code>0x16</code> is exactly the <code>ident</code> from that run&rsquo;s <code>MACH_DEBUG_WRAP</code> trace</strong> (<code>[WRAP] change kq=9 ident=0x16</code>), and <code>0x17</code> is this kevent&rsquo;s own ident &mdash; tying the freed allocation to the very registration traced being created and deleted. The fault itself:</p>
306+
307+
<pre><code>&lt;+16&gt;: ldp x10, x8, [x0] ; x10 = word0 = 0, x8 = word1 = 7
308+
&lt;+24&gt;: mvn x9, x8 ; _dispatch_wref2ptr(du_owner_wref)
309+
&lt;+28&gt;: ldrb w8, [x10, #0x9] ; ← FAULTS: byte at 0x0 + 9</code></pre>
310+
311+
<p>A NULL+9 read &mdash; <code>dux_type(du)-&gt;dst_action</code>, <code>du_type</code> at offset 0. <strong>Fix A is the correct fix; the &minus;22 alternative is ruled out.</strong></p>
312+
313+
<h3>What the kernel change did <em>not</em> do</h3>
314+
<p>The same measurement re-ran the crash rate across the kernel boundary:</p>
315+
<table>
316+
<tr><th>Kernel</th><th>Crashes / 30</th></tr>
317+
<tr><td><code>20260831-232019</code> &mdash; before #167 / #168</td><td class="bad">13</td></tr>
318+
<tr><td><code>20260901-032651</code> &mdash; with #167 / #168</td><td class="bad">9</td></tr>
319+
</table>
320+
<p>Statistically indistinguishable (z &asymp; 1.07, p &asymp; 0.28). <strong>#168 changed nothing measurable</strong>, confirming from the other direction what the bisect concluded from the dates. Note also that the crashing runs print <code>SC-RUNLOOP-OK</code> before dying &mdash; the test passes, then the manager thread faults at exit.</p>
321+
291322
<h2 id="gnustep">10. The GNUstep question dissolves</h2>
292323

293324
<p>The premise behind &ldquo;bring CFMachPort in side by side with GNUstep&rdquo; is that the two would collide. <strong>They never meet.</strong></p>

0 commit comments

Comments
 (0)