Repository navigation
S5: The full plane set, and the OSS-Fuzz replay #431
Description
The rest of the plane set, and the OSS-Fuzz replay.
S1 shipped bounds and lifetime. This adds the type plane with its granule compression, the byte granular init plane with the padding rules, the epoch plane, restrict checking and sub object bounds. With those in place the checker covers every class in the taxonomy rather than the two easy ones.
The second half of the milestone is the OSS-Fuzz replay, which spec/safe-memory/12-corpus-and-evidence.md section 12.7 calls the highest expected value activity in the project relative to its cost. Replay every OSS-Fuzz corpus for every tier 1 project against a Tier D build. The inputs already exist and the harnesses already exist, so the cost is compute rather than engineering. The classes we add over ASan, which are post quarantine use after free, intra object overflow, uninitialized reads and type confusion, are exactly the ones those corpora have been exercising for years without a checker present that could see them.
What gets built
- The type plane in the runtime, with granule compression, in Add the type plane to the safety runtime #902
- The type plane mapped over every watched region, with judgement J3 and the two judgements that record it, in Map the type plane over every watched region #905
- The front end naming the type of every access, which is what a
meta_typenames a type with, in Name the type of every access in the front end #914 - The compiler's half of the type plane, first part: the judgement a store makes, in Record the type of every store in the type plane #923
- The compiler's half of the type plane, second part: the judgement a copy makes, in Carry the effective type of a copy to what it wrote #929
- The compiler's half of the type plane, third part: the question an access asks, in Ask the type plane whether the bytes agree with the type read #935
- The init plane in the runtime, a bit for every byte of storage, in Add the init plane, a bit for every byte of storage #936
- The init plane mapped over every watched region, with the judgements that record it, in Map the init plane over every watched region #937
- The compiler's half of the init plane, with the padding rules of
spec/safe-memory/09-type-init-and-races.mdsection 9.3, in Add -fsafety-init=padding|nopadding, the padding rule of section 9.3 #948 and Ask the init plane whether the bytes an access reads were written #952 - The epoch plane in the runtime, the stamp and the Lamport clock and the plane's arithmetic, in Keep an epoch plane in the runtime #988
- The epoch plane mapped over every watched region, with the judgement that records it, in Map the epoch plane and record who wrote a pointer word #994
- The lock edge that carries an ordering between threads, in Carry an ordering between threads across a lock #1012
- The thread edges, so creating a thread and joining one carry an ordering too, in Carry an ordering across creating and joining a thread #1017
- The condition variable and semaphore edges, which the lock rows cannot stand in for, in Carry an ordering through a condition variable and a semaphore #1022
- Judgements C2 and C3, which is the reader over the epoch plane, numbered J9 in the model, in Report a pointer word two threads wrote with nothing between them #1024
- Judgement C4, which is a lifetime refusal carrying the epoch witness that names both threads, in Name both threads when a race caused a use after free #1026
- The compiler's half of the epoch plane, part one, which is the recording a store of a pointer makes, in Record which thread stored a pointer under -fsafety-races #1029
- The compiler's half of the reader, so an access asks
check_raceunder-fsafety-racesand not only the wrappers fill the plane, in Ask the epoch plane who was there first at an access #1033 - The atomics edge, which is not a call and so cannot be interposed, and is the gate on this being on by default, in Emit a synchronization edge around an atomic under -fsafety-races #1039
- The fence edge, which orders against every thread rather than against an object and so has no address to be keyed on, in Emit a synchronization edge around a fence under -fsafety-races #1046
- The aux slot's format, which is what a capability in memory is written in, in Give the aux slot a format #1071
- Finding the aux slot for a word of memory, and reading and writing one, in Find the aux slot for a word and read and write one #1076
- The runtime end of
cap_storeandcap_load, which is the policy over that format and the pair of names generated code is compiled against, in Give cap_store and cap_load their runtime end #1083 - Judgement C1, the torn store, in Report a pointer stored beside somebody else's capability #1145
-
restrictchecking, in Check the restrict contract in the runtime, which is J8 #973, Give the IR the four instructions restrict checking needs #978 and Check the restrict contract in the compiler, which closes J8 #984 -
-fsafety-subobject, the type compatibility form of section 9.4, in Make a store ask the plane when the build says the member matters #968 - Rule verification in
rucc-verifyacross the wholesafety/namespace, finished in Prove the alignment half of a bounds check instead of comparing #985 - Randomized elimination fuzzing, per
spec/safe-memory/14-verification.mdsection 14.4, in Generate programs nobody wrote and hold both builds to them #966 - The OSS-Fuzz replay over at least six tier 1 projects, in The OSS-Fuzz replay over the six tier 1 projects, which is the last box of S5 #1499: brotli, zlib, SQLite, Lua, zstd and libjpeg-turbo, with the scoreboard in
docs/REPLAY.md - Fixes for every bucket 1 finding of the replay in our forks, Do not read the data register of a preformatted insert sqlite#1, Form decoder pointers only once they are known to be in bounds zstd#1 and Zero the decompressor's sample rows and fix the fuzzer's precision branch libjpeg-turbo#2, each fork rebased on upstream once a day, with the triage pointing at them in Point the replay's triage at the fork fixes and test libjpeg-turbo's precision ranges #3113
- S6, an access through a null pointer refused as J1 rather than left to the hardware, in safety: refuse an access through the capability of a null pointer #3147
- S1 inside the allocator's rounding, where an access is held to the size that was asked for rather than to the size class, in safety: hold an access to what was asked for rather than to the allocator's class #3166
- S2 and S3, an overflow from one local or global into the next, refused through a capability the compiler builds from the object's address and declared size, in Give a local and a global a capability of their own #3202
- Y4 and Y5, a call through a pointer with the wrong number of arguments and data called as a function, in Check a call through a pointer against what it reaches #3209
- T4, a pointer to a local used after its function returned, through a witness word in the frame, in Refuse a pointer to a local once its function has returned #3219
- T6 on mappings:
mmap,munmapandmremapare interposed and a mapping is an instance in an arena of the runtime's own (Refuse a pointer into a mapping once it was unmapped #3226) - Y6 on locals, a read of a local nothing wrote (Refuse a read of a local nothing wrote #3230)
- A pointer to a block scope local used after the block, through a witness word per local that its block shuts and its declaration opens, in safety: refuse a pointer to a block scope local used after its block #3236
- T4 through a wrapper and through an inlined call, so a local's address returned and handed to
printformemcpy, or returned by a function the optimizer inlined, is refused, in safety: refuse a read of a returned frame inside a wrapper and after an inlined return #3432 - Y6 on bytes taken by
__builtin_allocaor a variable length array, begun where they are taken and forgotten when the scope gives them back, in Begin the bytes an alloca or a variable length array takes, and forget them when the scope gives them back #3446 - Y6 on a local the compiler keeps in a register, through a flag carried beside it in the SSA form and asked at each read, in feat(safety): refuse a read of a register local nothing wrote, with a flag carried in SSA #3460
- S2 and S3 through a pointer stored in a word of the stack or of a global and read back, which keeps its capability in a table beside the stack plane, in safety: keep the capability of a pointer stored in a stack or global word #3610
- S2 and S3 inside a wrapper such as
memcpygiven a pointer read back out of memory, which hands the wrapper the capability beside the read, in safety: hand a wrapper the capability of a pointer read out of memory #3617 - S2 and S3 through a pointer in a structure passed by value, which travels as a pointer in its register and takes its capability with it, in Pass a structure of one pointer in a register as a pointer #3625
- T4 through memory, a pointer to a local of a closed block read back while its function still runs, in Keep the capability of a local whose block was left when it is read back from memory #3663
- S8 through the wide string functions,
wcscpy,wcscat,wmemcpyand the rest interposed with their lengths counted in characters, in safety: interpose the wide string functions, counting in characters #3667 - S8 against an array member, a library call handed the address of one judged against the member the way
_FORTIFY_SOURCEjudges it, in Hold a library call to the array member it was handed #3674 - An unterminated string in a local handed to a call, the local filled with a pattern on the way in so the walk reaches the end of the buffer, in Fill the locals a function lets go of with a pattern under safety #3706
- The Juliet per row numbers, from a
cargo xtask julietthat builds every case at every tier and lists what it missed, in xtask: juliet, every case at every tier, and docs/JULIET.md with the numbers #3329, with the scoreboard indocs/JULIET.md
Exit criterion
The Juliet per row numbers of spec/safe-memory/14-verification.md section 14.6 published for every row and every tier, with missed cases enumerated by test id rather than summarized.
The OSS-Fuzz replay run over at least six tier 1 projects.
At least one upstream confirmed bug found by a class no existing tool covers. That is the criterion that actually matters here.
Activity
Point the open gaps at the milestone that will close them (#527)
tamnd commentedon Sep 10, 2026
Filed #849 against this milestone, for the two biggest rows in the safety census on SQLite. Both are in
dischargeand both say a fact was thrown away because a call stood between the check that established it and the check that wanted it, 2194 bounds checks at 626 sites and 2177 liveness checks at 625.I priced the row before filing it, by marking every call
NOFREEwhatever it does, which is unsound and only there to get a number. It removes 1776 calls to__rucc_check_boundsand 1772 to__rucc_check_livefrom the object and 240016 bytes, which is seven and a half per cent of every check in there, and it leaves a residue of 68 at 35. So a good answer very nearly finishes the row.It lands here rather than on S4 because the cheap version does not work. Widening the never frees name table by 49 names moves the row by one check, since 1516 of the 1551 callees that fail to clear are defined inside the amalgamation itself and nearly all of them reach
realloceventually. What the row needs is the freed and escaping parameter summaries from section 7.5 plus the escape analysis to use them at the call site, which is the work this milestone is already carrying.split: say why the measured walk keeps its alignment refusal (#859)
Add the type plane to the safety runtime (#902)
tamnd commentedon Sep 11, 2026
#902 is in, which is the runtime half of the first box.
rucc_safe_rt::typesholds what a byte was last stored through, which is the fact four of document 03's type classes and one of its spatial classes are decided by.It is C 6.5 rather than an approximation of it. A store records the type it stored through, an access asks whether what it is about to read agrees, a byte nothing has stored through agrees with everything, and both halves of the character rule are in one function, so the byte at a time copy idiom works rather than being a special case bolted on later. A
memcpycarries the source's types to the destination, which is the rule that makes punning legal where C says it is and is also what catches copying astruct Aover astruct Band reading it back as aB.The granule is eight bytes and the slot is four, with a side entry of eight identifiers for a granule whose bytes disagree. That is the
4/g + 4hof document 05 section 5.2.3, and eight is the number section 5.2.5 measured rather than the one that looks natural. On a sixty four bit target a pointer, alongand adoubleare all eight bytes, so a sixteen byte granule holds two distinct types and a structure alternating two of them disagrees in every granule it has:The budget is 1.25, so the choice is not close.
Two decisions in it are worth recording because they are trades rather than readings of the spec.
A granule that has once disagreed keeps its side entry for the life of the program, and a later store covering the whole of it writes one type over all eight bytes rather than folding the granule back up. That makes the allocator one
fetch_addwith no free list, no lock and no ordering to get right, which matters because this is the first structure on the plane side of the runtime that anything allocates at all, and the planes are deliberately unsynchronized today. The cost is one indirection on later reads of that granule and thirty two bytes that stay out. Reclaiming them wants the free list and the spin lockrucc_safe_rt::allocalready has, and that is not worth writing before a measurement says who folds granules back up often enough to care.A granule that has to disagree and cannot get an entry is recorded as untyped, which every access is permitted against, so running out of side table thins the plane's coverage and never makes it wrong. That is the direction document 09 section 9.1 already picks for bytes uninstrumented code wrote, and it is the only one available given that the false positive rate is a release blocking property.
The class people ask about first is not in this plane and is free: reading a word that was never a pointer and dereferencing it is caught by the aux plane, since an aux slot whose payload was never a pointer holds the bottom capability and the first access through it fails. That is why document 04's plane table asks for a pointer slot only at Tier E.
Nothing calls into it yet. The store that makes the judgement and the access that asks the question are the compiler's half, which is the new box under this one and the next PR.
Map the type plane over every watched region (#905)
tamnd commentedon Sep 11, 2026
#905 is in. The plane from #902 is now mapped over every region the monitor watches, and
rucc_safe_rt::checkhas judgement J3 beside the three checks it already had, with the two judgements that record what J3 asks about:__rucc_check_type,__rucc_meta_typeand__rucc_meta_type_copy.A region's reservation is four spans now, the lifetime plane, the type plane, the type plane's side table and then the region, which is one byte of plane per byte of region against document 05 section 5.2.3's budget of 1.25. The side table is one entry for every eight granules, which is the 12.6 percent heterogeneity section 5.2.5 measured on SQLite rounded the safe way. The reservation went from one and a half times the region to two and a half, and that is address space rather than memory, since the mapping is anonymous and a plane nobody touches costs no pages.
An adopted region gets all three the same way, so a region this crate did not reserve is watched exactly as closely as one it did. A region that cannot have all of them is not taken on at all rather than taken on half watched.
The thing worth recording out of this one is a rule I would have gone on to get wrong. A fresh instance has to forget what its bytes used to be, which is the other half of judgement J4 and is C's rule that allocated storage takes its effective type from the first store. Without it a block handed out again still says what the previous occupant stored through, and the new owner's first honest read is reported as type confusion. That is a false positive on ordinary code, which for this plane is the failure that matters, and it is not a corner case: the third test written here hit it immediately, because the allocator hands blocks straight back out.
It is cleared over the whole block rather than the request. The bytes between the request and the end of the block are the allocator's rounding, they share granules with the request, and leaving them would turn an overflow inside a block into a type report instead of the bounds report it is.
The 137 program safety suite is the check that matters here, since this moves where every region's lifetime plane lands, and it is unchanged at 46 refused and 60 silent with 0 accounting divergences.
Next is the compiler's half, which is emitting
meta_typeon a store andcheck_typeon an access. The IR already has both opcodes and thePlaneNodevocabulary from S0, and the runtime's vocabulary was written to line up with it:NoType,Character,PointerSlot(k)andType(node)are the runtime's untyped, character, pointer byte and interned entries.tamnd commentedon Sep 11, 2026
#914 is the prerequisite for the compiler's half of the type plane, and it turned out to be a piece of work of its own rather than a line in that change.
PlaneNode::Typenames a type with an aliasing node, which is the same node the optimizer's layer 3 walks. That is the right design and it is what section 9.1 asks for, since it means the plane's vocabulary is exactly the compiler's and a report can name a type in the spelling the source used. The problem was that the front end named no types at all:MemInfo.tbaawasNoneat every construction site in lowering, so there was no node for ameta_typeto carry and nothing to intern one against.So #914 builds the tree.
charis the root, every other scalar hangs directly under it, a signed type and its unsigned counterpart share a node, an enumeration shares with the integer type it is represented in, every object pointer is one node, and an aggregate gets none. That maps onto the runtime vocabulary I wrote in #902 exactly:interned(n)is a node,CHARACTERis the root,UNTYPEDis the absence of one, andpointer_byte(k)is the byte of a pointer, which is the one part of the vocabulary the tree does not have an opinion about because it is about storage rather than about C types.The node is found by a canonical spelling of the type and never by the order the walk met it, which section 8.2 of the optimizer's alias document asks for so that LTO does not silently gain disambiguations when two modules are merged. That matters here for a second reason: a
check_typecompiled in one file and ameta_typecompiled in another have to agree about whatstruct sqlite3 *is, and agreeing by name is the only way they can.What is left for the compiler's half is the emission itself, which is a store emitting
meta_typewith the node for the type it stored through, an access emittingcheck_typewith the node for the type it is about to read, and amemcpyemittingmeta_type_copy. All three opcodes exist, the verifier already insists they name a plane node, andrucc_safety::insertis where they go beside the bounds and lifetime checks it already emits. The interesting decisions left are which accesses are worth asking about at all, since asking about every one of them is the cost document 13 is about, and what a store through a character lvalue does, which the runtime already has both halves of.The 137 program safety suite is unchanged at 46 refused and 60 silent, and accounting is at 0 divergences, which is what says this moved nothing that was already working.
Record the type of every store in the type plane
Record the type of every store in the type plane (#923)
tamnd commentedon Sep 11, 2026
#923 is merged, which is the first half of the compiler's side of the type plane. A
-fsafetybuild now records the type of every store it instruments, so the plane the runtime has been carrying since #902 finally gets written by real code rather than by tests.The entry a store records is a plane node rather than the aliasing node the front end attached in #914. Three of the things a byte can say are not types at all: it can be untyped, it can be a character, and it can be one byte of a pointer. A store through the root of the aliasing tree, which is the node named
char, records the character entry, because 6.5 says a character store sets no effective type. A store that names no type records the untyped entry rather than recording nothing, so a plane never goes on describing memory that is no longer there. Untyped is compatible with everything, so the cost of that is a missed check and never a wrong answer.The number the runtime sees is FNV-1a 32 of the canonical spelling of the type, biased past the values that are not types. A per object counter would have been smaller, but the plane outlives the object that wrote it, and a counter makes
int3 in one translation unit and 7 in the next, which would refuse correct programs. The cost of the hash is a possible collision, which is again a missed check, and that a report cannot printintyet. The spelling stays recoverable because a hash of a spelling is a spelling the compiler still has.The question an access asks is deliberately left out of this PR and is now its own box. A check against a plane that only some of the writes maintain reports on programs that are correct, so the question has to wait until the writes are complete. The gap that blocks it is a copy: the runtime already exports
__rucc_meta_type_copy, but there is no IR opcode for a copy to carry the source's effective type across, so amemcpywould silently drop the types it moved and the very next read would be refused. Union punning has to be answered too, since C permits reading a member other than the one last stored through.The refusal counts did not move, which is what a judgement that refuses nothing should do. The Linux run still says 46 refused, 60 silent and 0 divergences over 136 programs.
104 remaining items
tamnd commentedon Oct 9, 2026
#3421 is merged. A pointer made below or past a local or a global is now refused where it is made, as J2, because the derivation check takes the capability the compiler builds for those objects instead of asking only the planes, which cover the heap. On Juliet at -O2, CWE-124 goes from 570 to 710 detected and CWE-127 from 494 to 710, every case not set aside, with no false positives in any CWE. The
docs/JULIET.mdnumbers will be brought up to date in a follow up.tamnd commentedon Oct 9, 2026
#3426 is merged and gives row T9 a sweep. Under
-fsafety-leaks, at detect or kernel, every live block nothing the program can still reach is reported at exit under its own banner. Juliet CWE-401 at -O2 goes from 0 to 576 of 698 detected with no false positives, and enforce is not run for that row because document 03 gives it nothing to enforce.tamnd commentedon Oct 9, 2026
#3432 is merged and closes #3272. A pointer to a local whose function has returned is now refused when it is read inside a wrapper, as with
printLine(helperBad())where the helper returns a buffer of its own, and when the helper was inlined at -O2 and its buffer became a slot of the caller. The wrappers now ask the frame witness, the caller passes on the capability of a returned local even when it checks nothing through it, and the inliner ends the lifetime of each marked local where the inlined copy returns.A full Juliet run at -O2 on the branch had 0 false positives at every tier, and every case built at every tier. CWE-562 goes from 0 to 2 of 2 at detect, enforce and kernel. Ten more CWE-843 good halves are now refused for reading a block scope local after its block, which is a real use after the end of its lifetime in both halves, so those cases are set aside. Two CWE-126
CWE170variant 12 cases moved to missed at detect, because whether they are caught depends on a byte of the frame nothing writes. docs/JULIET.md gets the new numbers next.tamnd commentedon Oct 9, 2026
#3441 is merged and puts the run from #3432 into docs/JULIET.md: 7,311 detected, 863 missed, 480 set aside and 0 false positives out of 8,654 Juliet cases at -O2, at every tier. It was 6,050 detected and 2,203 missed in the run with #3329. What is left is 390 pointers that go through memory (#3269), 53 unterminated copies whose last byte is a zero nothing wrote and 240 reads of what nothing wrote (#3271), 76 wide string copies (#3268), 72 overruns that stay inside their struct (#3327), and 32 leaks whose block a global still points at, which stay counted as missed.
tamnd commentedon Oct 9, 2026
#3446 is merged. A read of bytes that
__builtin_allocaor a variable length array took and nothing wrote is now refused as Y6. The bytes are marked unwritten where they are taken, and__rucc_local_leftforgets them behind eachstackrestoreand before each return. The -O2 case first went through quietly because the optimizer steps the fill loop's pointer through a block parameter, and the pass took any block call as letting the local go. It now follows addresses through block parameters. Juliet CWE-457 at -O2 goes from 300 to 408 of 540 detected at every tier, and CWE-126 from 423 to 425. The full -O2 run has 0 false positives and 0 build failures in every CWE and tier.tamnd commentedon Oct 9, 2026
#3460 is merged. A local whose address is never taken lives in a register and has no bytes for the init plane, so a read of one that nothing wrote used to pass. The front end now keeps a flag next to each such local in its SSA form, false at the declaration and true after each assignment, and every read asks it. Where all paths agree the flag is a constant and the check goes away, so only reads that depend on the path cost a call. Juliet CWE-457 at -O2 goes from 408 to 498 of 540 at every tier, and the full Juliet run at -O2 has no false positives and nothing left unbuilt. The 42 cases still missed are all variants 63 and 64, which pass the address to a sink in another unit, and #3271 stays open for those.
tamnd commentedon Oct 13, 2026
#3610 is merged. A pointer to a local or a global that goes through a word of the stack or of a global, like the Juliet flow variants with a union, a static global or a pointer to a pointer, used to come back out with no capability, so the checks in the sink had nothing to go on. The runtime now keeps those capabilities in a small per thread table beside the stack plane, and at -O2 the hoisting and splitting of bounds checks reuse the loaded capability instead of building a wider one.
At -O2 Juliet misses 241 under CWE-121 where it missed 406, and 82 under CWE-126 where it missed 94, still with no false positives at any tier. That brings the total down to 487 misses out of 8,654. The safety suite and the accounting check show no change elsewhere.
What is left on #3269 is a pointer read out of memory and passed straight to
memcpyor another wrapper, which the handover does not pass on yet. That is 213 cases, and I will look at it next.tamnd commentedon Oct 13, 2026
#3617 is merged. A pointer to a local or a global that is read back out of a word of the stack or of a global and handed straight to a wrapper such as
memcpynow brings its capability along, so the copy is judged against the object's bounds. The handover only does this for a call to a wrapper, so other calls cost nothing more.Juliet at -O2 goes from 487 misses to 343 with no false positives at any tier: CWE-121 from 241 to 121 and CWE-126 from 82 to 58. What is left of the #3269 bucket is variant 67, where the struct is passed by value and the pointer is copied out of it a word at a time (47 under CWE-121, 6 under CWE-126), and the 16 CWE-843 cases. The rest of the misses are #3268 (wide strings), #3327 (overrun inside a struct), #3271 (CWE170 and CWE-457 across files) and the leaks kept in a global.
tamnd commentedon Oct 14, 2026
#3625 is merged. A structure whose register holds exactly one pointer member now travels as a pointer, the way clang lowers it, so the safety handover hands the callee the bounds of what the member points at. The handover also looks through a store and the load straight behind it, which the monitor's own records keep alive where the optimizer would have forwarded them.
Juliet at -O2 goes from 343 misses to 291 with no false positives at any tier: CWE-121 from 121 to 74 and CWE-126 from 58 to 53. No flow variant 67 case is missed now. What is left: #3268 wide strings (76), #3327 overrun inside a struct (72), #3271 CWE170 (53) and CWE-457 across files (42), leaks kept in a global (32), and the 16 CWE-843 cases on #3269.
tamnd commentedon Oct 14, 2026
#3663 is merged. A pointer to a local declared in a block that was stored into memory and read back after the block closed used to lose its capability at the load, because the runtime threw out the entry once the local was gone and then recovered bounds over everything. A block left while its function still runs keeps its witness serial, so the entry can only be that same local, and it is now handed on and refused by check_live as T4. Juliet at -O2: the 16 CWE-843 cases in the memory variants have both halves refused and are set aside with the rest of the family, CWE-843 is 0 missed, no false positives anywhere, and total misses go from 291 to 274.
tamnd commentedon Oct 14, 2026
#3667 is merged. The wide string functions are now interposed: seventeen rows from
wcslentowmemchr, each written like its narrow twin, withinterpose!taking a unit so counts and terminators are measured in characters. Awcscpyinto an alloca sized bystrlenof a wide string is refused under tests/safety. Juliet at -O2 detects all 76CWE135cases under CWE-121 and CWE-122 with no false positives, which brings the misses from 274 to 198. What is left: 52 unterminated copies under CWE-126 and 42 cross-file reads under CWE-457 (#3271), 72 overruns that stay inside their struct (#3327), and 32 leaks kept in a global.tamnd commentedon Oct 16, 2026
#3674 is merged. A call to
memcpy,memmove,strcpyor any other interposed function that is handed the address of an array member is now judged against that member, which is what_FORTIFY_SOURCEdoes with__builtin_object_size(p, 1). The call site leaves the member's length in a thread local note for the wrapper, and a small constant copy that overruns its member stays a call rather than being expanded inline. A member that is not an array and a trailing array are left alone.Juliet at -O2 now has no misses under CWE-121 and CWE-122, 2,400 and 1,480 detected with no false positives at any tier. That takes the 72 struct overrun cases off, so the total is 8,032 detected and 126 missed. What is left is the 52 CWE170 cases and the 42 cross file CWE-457 reads in #3271, and the 32 leaks kept in a global.
tamnd commentedon Oct 16, 2026
#3706 is merged. Under
-fsafetya local whose address leaves its function is now filled with0xfestraight behind itsalloca, the byte-ftrivial-auto-var-init=patternuses, unless the program set-ftrivial-auto-var-inititself. A copy that never wrote a buffer's last byte used to stop on a zero an earlier frame left there, and now the%swalk runs to the end of the buffer and is refused. That detects all 52CWE170cases under CWE-126 at every tier, and 6 CWE-457 cases where an uninitialized pointer handed to another file is now refused when it is used. Every Juliet CWE was run at -O2 with no false positives, and the misses are down to 68: the 36 CWE-457 cross-file reads and the 32 leaks kept in a global.tamnd commentedon Oct 16, 2026
#3708 is merged. The instrumented libraries job on main failed on zlib's gzread.c at
-O2with E0652, saying short-circuit broke the loop forest of gz_decomp. The forest was fine. It was kept through a merge and listed one block in a different place than a fresh build, and the-Zverify-eachcheck compared the two with==. It now compares them the order-free way the debug assertion already did, and every zlib source compiles with-O2 -Zverify-eachagain. A three block loop test covers it.tamnd commentedon Oct 16, 2026
#3709 is merged. The elimination job's one problem was a block scope local written through in another file at
-O2, which went unrefused. That loop is split where__rucc_extentsays the object ends, and for a named object the answer came from the capability's bounds alone, so the unchecked half also skipped the liveness check. The runtime now gives a local whose frame returned or whose block closed no room, so the loop runs checked and is refused as J1.cargo xtask accountingis 208 programs with 0 problems now, and the safety suite is unchanged at 116 refused and 90 silent.