view: require an unsafe LifetimeParametric marker on OwnedView views - #381
Draft
iainmcgin wants to merge 1 commit into
Draft
view: require an unsafe LifetimeParametric marker on OwnedView views#381iainmcgin wants to merge 1 commit into
iainmcgin wants to merge 1 commit into
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
OwnedView::decode transmutes the Bytes slice to &'static [u8] and hands it to
V::decode_view with only V: MessageView<'static> as a bound. Nothing required
V to be parametric in the buffer lifetime, so a hand-written
impl MessageView<'static> for MyView (or its own Debug/Clone/PartialEq/Drop/
Serialize impl, which OwnedView invoked on the 'static-typed view) could copy
a &'static str out of the buffer into longer-lived storage and read it after
the OwnedView dropped.
Add `pub unsafe trait LifetimeParametric: ViewReborrow {}` whose # Safety
section states the contract, require it on every OwnedView constructor and
on HasMessageView::decode_view_handle{,_with_options}, and route OwnedView's
Debug/PartialEq/Eq/Serialize/to_owned_message through ViewReborrow::reborrow
so the view's impls only ever run at the real buffer lifetime. Split the
inherent impl so bytes/into_bytes need no bound and reborrow/to_owned_message
need only ViewReborrow; put a Debug bound on ViewReborrow::Reborrowed so
OwnedView<V>: Debug holds for every V: ViewReborrow without a for<'b> clause.
Codegen emits `::buffa::unsafe_impl_lifetime_parametric!(FooView)` for every
eager view; the macro keeps generated output valid under forbid(unsafe_code).
Regenerate the checked-in buffa-types and buffa-descriptor views, add a
compile_fail doctest for a non-parametric capturing view and a Miri-targeted
positive test wired into the CI Miri step, and update the guide and DESIGN.md.
Closes #376.
iainmcgin
force-pushed
the
iain/issue-376-ownedview-contract
branch
from
August 27, 2026 08:00
7f0ef17 to
b827498
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OwnedView::<V>::decode/decode_with_optionstransmute theBytesslice to&'static [u8]and hand it toV::decode_view, withV: MessageView<'static>as the only bound.MessageViewis safe and unsealed, and nothing requiredVto be parametric in the buffer lifetime — so a hand-writtenimpl MessageView<'static> for MyView(or its ownDebug/Clone/PartialEq/Drop/Serializeimpl, whichOwnedViewinvoked on the'static-typed view) could copy a&'static strout of the buffer into longer-lived storage and dereference it after theOwnedViewdropped: a use-after-free reachable from safe code. Generated views are all parametric, so only bespoke hand-written views were exposed. Input-side sibling of GHSA-9pwq-gcrx-wghh (#154).Fix
pub unsafe trait LifetimeParametric: ViewReborrow {}inbuffa::view. Its# Safetysection states the contract: no borrow derived from the&'static [u8]handed todecode_viewmay outlive the view or be exposed beyond a borrow of it — across theMessageViewimpl, the view'sDrop/Clone/Debug/PartialEq/Hash/Serializeimpls, and aViewReborrowimpl that genuinely shortens the lifetime. Parametric impls satisfy it by construction; that is what codegen emits.OwnedViewconstructor (decode,decode_with_options,from_owned,from_parts) andHasMessageView::decode_view_handle/decode_view_handle_with_optionsrequire it.bytes/into_bytesneed no bound;reborrow/to_owned_messageneed onlyViewReborrow.OwnedView'sDebug,PartialEq,Eq,Serializeandto_owned_messagego throughViewReborrow::reborrow, so the view's own impls only ever run at the real buffer lifetime.ViewReborrow::Reborrowedgains aDebugbound soOwnedView<V>: Debugholds for everyV: ViewReborrowwithout afor<'b>clause in generic code.::buffa::unsafe_impl_lifetime_parametric!(FooView)for every eager view (the lazy family has noOwnedViewsurface). The macro rather than a literalunsafe implkeeps generated output valid under#![forbid(unsafe_code)]. Checked-in generated code inbuffa-typesandbuffa-descriptoris regenerated.compile_faildoctest on the trait shows a non-parametric capturing view rejected at thedecodecall;owned_view_lifetime_parametric_contractdrives decode/Debug/Clone/PartialEq/reborrow/to_owned_message/drop on a hand-written view and is added to the CI Miri step.Breaking change
Hand-written view types used through
OwnedViewmust implementViewReborrow(now a supertrait, and its view must beDebug) and addunsafe impl LifetimeParametric for MyView<'static> {}after auditing against the contract. Code generated by buffa 0.9 or earlier must be regenerated. Generic code callingdecode_view_handleaddsM::View<'static>: LifetimeParametricat the use site (the bound cannot live on the trait — the GAT normalization error still reproduces on 1.95).examples/bsr-quickstartis pinned to the published BSR plugin and was already stale against the workspace before this change; it picks this up when that plugin is next released.Closes #376.
Reported by HackerOne researcher waynezinn.