Skip to content

Propolis extract - #19

Merged
wfchandler merged 6 commits into
mainfrom
propolis-extract
Oct 22, 2026
Merged

wfchandler merged 6 commits into
mainfrom
propolis-extract

Conversation

@wfchandler

Copy link
Copy Markdown
Collaborator

Fix bundle extraction for propolis-server, add required library versions.

wfchandler and others added 6 commits October 21, 2026 21:29
TypeTable::size_of returns None for every Opaque, including one whose
size DWARF recorded, though its doc comment says only an Opaque without
one is unknown and the read side already sizes an Opaque by the size it
records. A type exegesis demotes for a member that overruns it keeps
its byte size so that whatever embeds it still lays out, but the
validator's semantic path rule takes it for unsized: a path that
crosses or lands on one is refused, and the whole bundle with it.

propolis-server is such a target. rustc describes a fn item, which
takes no bytes, as an 8-byte pointer, so crucible's
`Map<Range<u8>, fn(u8) -> ClientId>` declares 2 bytes and a member
ending at byte 10. It is demoted, and an async fn holding one across
an await records a coroutine local that fails validation:

    semantic path: inline member is unsized or out of bounds

Return the recorded size. The demotion pass's comment gave the size
loss as its reason for collecting the demotions first; the collecting
stays, and the comment gives the reason that remains. The semantic
path test that used a sized opaque as its unsized case now uses one
with no size, beside a sized one that overruns its parent and one that
fits and validates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
coroutine_states lists every member of a state's payload as a local
without holding its path to the validator's rule, the one checked_path
holds every other semantic path to. A local whose type has no known
size, such as the `<unresolved>` opaque standing for a DIE the reader
did not model, is written into the layout and the validator then
refuses the bundle: one unreadable local costs every type in the
binary.

Hold each member's path to checked_path, list only those it accepts,
and return a decline naming each one left out, which the record carries
among its issues as it does a select! arm that cannot be placed. The
rest of the layout binds as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A semantic path that reaches an unsized member, or one past its
parent's end, fails validation with "inline member is unsized or out
of bounds", naming neither the type nor the step. A bundle that fails
validation is never written, so there is nothing to look the failure
up in afterwards.

Report the root type and the step, and for a member out of bounds its
size and offset against the parent's size. The semantics checker, which
holds the bundle's strings, adds the path by name, as `tokio-info dump`
writes a walk's steps, and the root type's name. The propolis-server
failure would have read:

    semantic path for type 17040: step 1 reaches type 17047, which is
    unsized (path <3>.iter from
    crucible::io_limits::{impl#2}::claim::{async_fn_env#0})

The endpoint check's "unsized or opaque endpoint" becomes "endpoint
type N is unsized", since an opaque of recorded size is now sized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
propolis-server links tokio-rustls 0.25.0, through dropshot 0.16.7,
beside 0.26.4. The stream and handshake reviews start at 0.26.0, so
extraction refuses it unless --allow-unsupported.

0.25.0 is the only 0.25 release. Its client and server streams hold the
members 0.26.0's do and forward reads, writes, flushes and shutdowns to
`io` the same way. It implements no vectored write, so tokio's default
writes the first non-empty buffer through `poll_write`, which parks
where any write does; 0.26.0 adds the vectored writes and moves the
client's early-data write into a helper without changing it. Its
MidHandshake has no SendAlert, which 0.26.0 adds. Handshaking, the one
variant the handshake rule navigates, holds the stream as in every
later release, Stream::handshake is the same text, and Connect, Accept
and their fallible pair are newtypes in src/lib.rs that poll it and
nothing else.

Lower both reviews' floors to 0.25.0 with its files' checksums, and
name each for its new floor, as every review is named. The matrix
goldens record the family names and change in those alone.

propolis-server now extracts without --allow-unsupported. A 0.25
stream reaches a rustls 0.22 session, which the rustls review, from
0.23.23, still refuses; propolis-server holds no such stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tokio-rustls 0.25 stream holds a rustls 0.22 connection, and the
session review starts at 0.23.23, so a binary holding one, such as a
dropshot 0.16 TLS server, is refused unless --allow-unsupported.

rustls 0.22.0 through 0.22.4 keep every word the review reads as the
same member of the same type but two. CommonState has no
has_sent_close_notify: send_close_notify queues the alert and records
nothing, and only send_fatal_alert sets sent_fatal_alert, which so
means a fatal alert alone. And ChunkVecBuffer has no prefix_used:
consume drains the bytes written out of the first record, so every
byte the ring holds is unsent. The flags' assignments, the state's
error, the record counts and the ring are otherwise the same text;
0.22.4 sets has_received_close_notify only once application data may
flow, as 0.23 does, and earlier 0.22 releases for any close_notify,
which the flag still reports as the peer's.

Extend the review to two spans, 0.22.0-0.22.4 and 0.23.23-0.23.45,
with 0.22's checksums, and name it for its new floor. The span the
release falls in selects the layout, and 0.22's leaves both words
out. Two reviews of rustls would each hold every lockfile to its own
range, so a release inside one would be reported outside the other.

Both words become optional in the bundle (format 106). A connection
without the flag is failed by its fatal alert flag alone, as 0.22
means it, and never closing for a close_notify it cannot show; a ring
without the prefix counts every byte unsent. The matrix goldens
record the family name and change in it alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mask_core masks how many records tls-conns' stalled client wrote and
how many bytes it holds unsent, as the kernel decides both, but keeps
the count of unsent records, taken to be the program's. It is not:
the client writes 64 KiB at a time, four records, until the socket
refuses one, and how many of that write's records stay unsent is
where the send buffer filled inside it. Linux leaves one; illumos
left one when the goldens were blessed and leaves three now, in every
capture, from main's hansei as from this one and under rustls 0.23.41
as under 0.23.45, so the illumos core golden fails on main too.

Mask the count with the bytes, singular byte included. What the
program decides still shows: a connection holding nothing unsent
names no unsent records, so the masked line keeps it apart from one
that holds some. The three platforms' tls-conns-tasks-exec-trace
goldens change in that count alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wfchandler
wfchandler merged commit 2afeeb1 into main Oct 22, 2026
5 checks passed
@wfchandler
wfchandler deleted the propolis-extract branch October 22, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant