Repository navigation
docs(maul): let a caller-built input point where its builder chose - #120
Conversation
maul-ui's audit under #119 found muiAccessUpdate, which the library fills but the header also documents as input a host builds. To the call that takes it, its pointers are caller memory, so it is not a breach; the rule now says so, and that such a call still checks every count, offset and index it can.
siracozmen01
left a comment
There was a problem hiding this comment.
Review at 6a06af1: changes needed (1). The new sentence opens the loophole you asked about.
1. The exemption depends on what the header says, not on what the call does. "So may one the header also documents as input a caller builds" lets any struct escape the rule through one header sentence. The weak point is "checks every count, offset and index it can": a call that cannot check something can say so and still qualify.
- Concrete case: maul-locale adds "a caller may also build a
mlocTimeZone" tozone.h. The zone calls keep trusting the zone and primary indexes insidezones, because those bytes were validated when the data was opened, and the new sentence would cover them. That is exactly #334's bug. The same goes formuiDrawListormnavPathif their headers gain such a line. - What separates
muiAccessUpdatefrommlocTimeZoneis how the call reads through the struct, not who built it:muiAccessTree_Applytreats the update's arrays like any buffer argument: it reads only inside the ranges the struct's own pointers and counts state, and checks every value it reads from them as untrusted.- A zone call follows indexes it finds inside the bytes beyond any extent the struct states, trusting an earlier validation. That is the case the rule has to forbid.
- Please tie the exemption to that behavior. For example: "So may one the header also documents as input a caller builds, such as an accessibility update, provided the call that takes it reads through it as through any buffer argument: only within the ranges its own pointers and counts state, checking every count, offset and index it reads there as untrusted, and relying on nothing the library validated earlier. Its pointers and counts are then the caller's to keep valid."
- With that wording, a header sentence alone exempts nothing.
muiDrawListandmnavPathstay fill-only, andmlocTimeZonestays under the rule even if its header calls it caller-built. maul-ui's check for #339 becomes concrete: Apply must never read outside the update's stated ranges and must never trust their contents.
Read, not run.
siracozmen01
left a comment
There was a problem hiding this comment.
Re-review at 2107c76: ready. My finding is resolved in the rule.
- The exception now holds only "provided the call that takes it reads through it as through any buffer argument: only within the ranges its own pointers and counts state, checking every count, offset and index it reads there as untrusted, and relying on nothing the library validated earlier". A header sentence alone no longer exempts a struct.
mlocTimeZone's calls still rely on the zones unit having been validated at open, so it stays under the rule, and a struct the library alone fills (muiDrawList,mnavPath) qualifies only if the call that takes it back really reads it this way. - "Its pointers and counts are then the caller's to keep valid" puts memory validity where it is for any buffer argument.
Recommended, not blocking: the body still describes the earlier wording. Its Change says the call "still checks every count, offset and index it can without trusting the pointers", and its Verification says the same in short. The body becomes the squash commit message, so it would record the weaker rule. Please bring the Change line in line with the file's "provided ..." clause.
Read, not run.
## Problem `mtwnClip_GetTrack` fills an `mtwnClipTrack` whose track, and whose easings' linear() points, point into the clip's storage. `mtwnClip_GetNode` fills a timing whose linear() points do too. The header said only that the track "references the clip's storage, valid while the clip lives". It did not say that the values layer takes such a track back as caller-built input, whose pointers and counts are the caller's to keep. With `track.path.count` raised to 2^20, `mtwnSampleTrack` reads past the clip's keyframes (#297). By .github v0.0.51's rule on state a caller holds (Rawframe-Project/.github#120), a struct the header also documents as caller-built input may point into the library's data. That holds only if the call taking it reads through it as through any buffer argument: only within the ranges its own pointers and counts state, checking every value it reads there, and relying on nothing validated earlier. ## Change - The `mtwnClipTrack` comment says what points into the clip's storage, that it is valid only while the clip lives (unchanged, since a sealed clip refuses every change), and that the values layer takes it as input the caller built. - `mtwnClipNodeInfo`'s timing says its linear() points are the clip's. - `docs/user/clips.md` says the same where it introduces `mtwnClip_GetTrack`. ## Change of results None; comments and the guide only. ## Verification Each of `mtwnSampleTrack`, `mtwnSamplePath`, `mtwnComputeTiming` and `mtwnSampleEasing`, read against the rule on main 64415a7: - **Reads stay within the stated ranges.** - The segment search (`FindSegment`, `src/path.c:386`) is a binary search in `[0, count - 2]`, whatever the offsets or lengths hold. - A segment reads `keyframes[i - 1]` only for `i > 0` and `keyframes[i + 2]` only for `i + 2 < count`. - The mixes are read at the segment's index. - linear() is a binary search in `[0, count)` (`src/css_easing.c:255`). - **Every value read is checked on each call.** - The track's and path's fields: `mtwnValidTrack` and `mtwnValidPath`, checked by every call. - The segment read: its kind, its easing as a maker's (`mtwnValidEasing`), and at constant speed not a step (`Reads`, `src/path.c:497`). - A color's or rotation's mix where the segment reads it (`MixReads`). - A timing whole, easing included (`mtwnValidateTimingWork`), at every `mtwnComputeTiming`. - **Nothing validated when the clip was made is relied on.** - Out-of-order offsets, lengths or linear() inputs, NaN or infinite values, and a zero width give a value perhaps not finite. They never give a read outside the arrays, a loop without bound, or a float-to-integer conversion. - A NaN local progress passes the steps and linear() samplers without a cast. - The arc-length loops are bounded by a stack size and a rule count (`src/path_length.c:304`) and by `s_newtonSteps`. - The clip's own sampling (`mtwnSampleTrackWork`) runs only on the clip's own copies, from the library. - No count limit is skipped: the makers and the sample checks both need at least 2 keyframes or points, and no MTWN limit applies to them. No gap was found, so no fix is needed. - `tools/check.sh changed` passes: clang-tidy on all 45 sources (a header changed), and linux-clang-20-asan 68 of 68. - The docs checks and the organization's docs check pass. Closes #297
## Problem `.github` v0.0.48 adds the family's `tools/tidy_filter.sh`, which prints clang-tidy's header filter for a build tree, and v0.0.50 and v0.0.51 add a conventions rule on how state a caller holds is checked (Rawframe-Project/.github#119 and Rawframe-Project/.github#120). This repository lacks it, carries an older `docs/internals/maul-conventions.md`, pins the reusable workflows at v0.0.42 and v0.0.45, and builds the filter itself in two places. ## Change - The family files are synced to v0.0.51 with `family_sync.py --write`: `tools/tidy_filter.sh` is added, and `docs/internals/maul-conventions.md` gains the rule that every clang-tidy call takes its header filter from it and the rule on state a caller holds. `AGENTS.md` lists `tidy_filter.sh` among the shared files. - Every reusable workflow and the docs-check action are pinned at `972ea41c6abf2652d3b067e57bf0713f1661cf0e # v0.0.51`. Between the old pins and v0.0.51 only `pr-title.yml` changed among the workflows, and its old pin, v0.0.45, already had that change. - ci.yml's clang-tidy step and `tools/check.sh`'s `tidy()` set `filter=$(tools/tidy_filter.sh <tree>)` on a line of its own, so a tree without the cache entry stops the step instead of tidying with an empty filter, and pass `--header-filter="$filter"`. `tools/check.sh`'s own `header_filter()` is gone. A changed header still tidies every source. ## Verification - `family_sync.py --check` from v0.0.51 reports 0 differences. - In a fresh clone of this branch, with only a header changed (`return value == value;` planted in `src/bits.h`), `tools/check.sh changed` fails on `misc-redundant-expression` in `src/bits.h`, and the ci.yml step's commands report it on the Release and Debug presets. `tools/tidy_filter.sh` on a tree whose cache lacks the entry exits 1. - `tools/check.sh changed` passes on the clean branch, and `shellcheck -S warning` is clean on `tools/check.sh` and `tools/tidy_filter.sh`. Closes #261
## Problem The family files from `.github` v0.0.51 (972ea41) had not reached maul-vfx, which is on v0.0.48 since #285. v0.0.49 and v0.0.50 are skipped because v0.0.51 holds their changes. The only family file that changes is `docs/internals/maul-conventions.md`, which gains the rule on how state a caller holds is checked (Rawframe-Project/.github#119 and Rawframe-Project/.github#120). ## Change - Ran `family_sync.py --write maul-vfx` from a clone of `.github` checked out at the v0.0.51 tag, not the live checkout. It writes `docs/internals/maul-conventions.md` only. - Pinned the reusable workflows and actions to 972ea41 (v0.0.51) instead of 7eeaaa4 (v0.0.48), in `ci.yml`, `pr-gate.yml` and `pr-title.yml`. The workflows and actions are the same at both tags. No code changes. The library already follows the new rule; the audit was done against the public headers: | The rule says | maul-vfx | | --- | --- | | A value struct a call takes back carries no raw pointer and size into the library's data | `mvfxEffectRefusal` (filled by the create, read and write calls, taken by `mvfxGetEffectFieldPath`) holds no pointer: inline tags and items with a `tagCount` checked against below 0 and past `MVFX_MAX_FIELD_DEPTH`. The ids are an index and a generation, checked on every call. | | A struct the library only fills in may point into its data | The views (`mvfxEmitterView`, `mvfxMeshView`, `mvfxGridView`, `mvfxLayoutView` and the rest), `mvfxEmitterPlan` and `mvfxParticleArrays` point into the library's data and are never taken back from the caller. | | A struct also documented as caller-built input may point where its builder chose, read as untrusted | `mvfxEffectDef`, which `mvfxReadEffect` fills into the caller's arena, is checked field by field by every call that takes it. The other defs, `mvfxSpawnRequest` and the output buffers point into caller memory. | | Caller memory a call keeps "as checked" is read only within the extents it recorded | `mvfxInstance_SetDepthImage` borrows the pixels. It checks the image once and keeps its own prepared copy of the extents, so later steps read within them. | ## Verification - `family_sync.py --check maul-vfx` from the same clone: 0 differences. - `git grep` finds no `.github` pin other than 972ea41 `# v0.0.51`. - `tools/check.sh changed` passes. Only documentation and workflows changed, so there is no build. - The organization's documentation check (`scripts/docs_check.py --strict`) from the v0.0.51 clone: ok.
Problem
The rule from #119 says a value struct a call takes back carries no raw pointer and size into the library's data. maul-ui's audit found
muiAccessUpdate. The library fills it frommuiContext_GetAccessUpdate, but the header also documents it as input a host builds. TomuiAccessTree_Applyits pointers are the caller's to keep valid, as with any buffer argument, so read literally the rule would call a documented caller-built input a breach.Change
maul/files/docs/internals/maul-conventions.md: one sentence after the fill-only exception. A struct the header also documents as caller-built input may point into the library's data, provided the call that takes it reads through it as through any buffer argument: only within the ranges its own pointers and counts state, checking every count, offset and index it reads there as untrusted, and relying on nothing the library validated earlier. A header line alone exempts nothing: a zone call that follows indexes the open validated stays under the rule.Verification
This is documentation only. maul-ui (#338, #339), maul-tween (#297) and maul-nav (#330, #331) close their issues under this rule only after confirming their calls read that way; any place that trusts an index found inside the data is a bug to fix.