Skip to content

Commit 347a893

Browse files
Akash ThotaAkash Thota
authored andcommitted
lang-v2: narrow CPI borrow validation override
1 parent 788d17e commit 347a893

4 files changed

Lines changed: 46 additions & 47 deletions

File tree

lang-v2/src/accounts/boxed.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ impl<T: AnchorAccount> AnchorAccount for Box<T> {
3636
(**self).account()
3737
}
3838

39-
fn cpi_requires_borrow_check(&self) -> bool {
40-
(**self).cpi_requires_borrow_check()
41-
}
42-
4339
fn exit(&mut self) -> pinocchio::ProgramResult {
4440
(**self).exit()
4541
}

lang-v2/src/accounts/slab.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -688,11 +688,6 @@ where
688688
type Data = H;
689689
const MIN_DATA_LEN: usize = Slab::<H, T>::MIN_DATA_LEN;
690690

691-
#[inline(always)]
692-
fn cpi_requires_borrow_check(&self) -> bool {
693-
false
694-
}
695-
696691
#[inline(always)]
697692
fn load(view: AccountView) -> Result<Self, ProgramError> {
698693
Self::from_ref(view)
@@ -717,6 +712,12 @@ where
717712
Ok(slab)
718713
}
719714

715+
#[inline(always)]
716+
fn try_cpi_handle_mut(&mut self) -> Result<crate::CpiHandleMut<'_>, ProgramError> {
717+
require!(self.account().is_writable(), ProgramError::InvalidArgument);
718+
Ok(crate::CpiHandleMut::without_borrow_check(self.account()))
719+
}
720+
720721
/// Fast-path `load_mut` after `create_and_initialize`. Skips
721722
/// `H::validate` and `validate_tail` (all tautologies post-init).
722723
///

lang-v2/src/traits.rs

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use {
1212
/// Obtained via [`AnchorAccount::cpi_handle`] (shared borrow) or by erasing a
1313
/// [`CpiHandleMut`] produced from [`AnchorAccount::cpi_handle_mut`].
1414
/// Most handles also participate in raw `AccountView` borrow validation before
15-
/// CPI. Wrappers with their own borrow-state discipline (for example Slab) can
16-
/// opt out so checked CPI doesn't reject sound Rust-exclusive access.
15+
/// CPI. Mutable handles from wrappers with their own borrow-state discipline
16+
/// (for example Slab) can opt out so checked CPI doesn't reject sound
17+
/// Rust-exclusive access.
1718
///
1819
/// Deliberately does NOT implement `Deref<Target = AccountView>` to
1920
/// prevent accidental use with pinocchio's checked invoke builders.
@@ -30,22 +31,16 @@ pub struct CpiHandle<'a> {
3031
/// erases into [`CpiHandle`] for invocation.
3132
#[derive(Clone, Copy)]
3233
pub struct CpiHandleMut<'a> {
33-
view: &'a AccountView,
34-
borrow_check: bool,
34+
handle: CpiHandle<'a>,
3535
}
3636

3737
impl<'a> CpiHandle<'a> {
3838
#[inline(always)]
3939
pub fn readonly(view: &'a AccountView) -> Self {
40-
Self::readonly_with_borrow_check(view, true)
41-
}
42-
43-
#[inline(always)]
44-
pub(crate) fn readonly_with_borrow_check(view: &'a AccountView, borrow_check: bool) -> Self {
4540
Self {
4641
view,
4742
writable: false,
48-
borrow_check,
43+
borrow_check: true,
4944
}
5045
}
5146

@@ -106,41 +101,49 @@ impl<'a> CpiHandle<'a> {
106101
impl<'a> CpiHandleMut<'a> {
107102
#[inline(always)]
108103
pub fn writable(view: &'a mut AccountView) -> Self {
109-
Self::with_borrow_check(view, true)
104+
Self::writable_with_borrow_check(view, true)
110105
}
111106

112107
#[inline(always)]
113-
pub(crate) fn with_borrow_check(view: &'a AccountView, borrow_check: bool) -> Self {
114-
Self { view, borrow_check }
108+
pub(crate) fn from_account(view: &'a AccountView) -> Self {
109+
Self::writable_with_borrow_check(view, true)
110+
}
111+
112+
#[inline(always)]
113+
pub(crate) fn without_borrow_check(view: &'a AccountView) -> Self {
114+
Self::writable_with_borrow_check(view, false)
115+
}
116+
117+
#[inline(always)]
118+
fn writable_with_borrow_check(view: &'a AccountView, borrow_check: bool) -> Self {
119+
Self {
120+
handle: CpiHandle::writable_with_borrow_check(view, borrow_check),
121+
}
115122
}
116123

117124
/// The account's on-chain address.
118125
#[inline(always)]
119126
pub fn address(&self) -> &'a Address {
120-
self.view.address()
127+
self.handle.address()
121128
}
122129

123130
/// Mutable handles always erase to writable CPI handles.
124131
#[inline(always)]
125132
pub fn is_writable(&self) -> bool {
126-
true
133+
self.handle.is_writable()
127134
}
128135

129136
/// Whether the underlying account is a signer on the transaction.
130137
#[inline(always)]
131138
pub fn is_signer(&self) -> bool {
132-
self.view.is_signer()
139+
self.handle.is_signer()
133140
}
134141
}
135142

136143
impl<'a> From<CpiHandleMut<'a>> for CpiHandle<'a> {
137144
#[inline(always)]
138145
fn from(handle: CpiHandleMut<'a>) -> Self {
139-
Self {
140-
view: handle.view,
141-
writable: true,
142-
borrow_check: handle.borrow_check,
143-
}
146+
handle.handle
144147
}
145148
}
146149

@@ -218,18 +221,6 @@ pub trait AnchorAccount: Deref<Target = Self::Data> + Sized {
218221
Ok(())
219222
}
220223

221-
/// Whether CPI handles derived from this wrapper should participate in the
222-
/// raw `AccountView` borrow checks used by `program::invoke[_signed]`.
223-
///
224-
/// Wrappers that keep live Rust references directly into account memory but
225-
/// bypass pinocchio's borrow byte (for example Slab) override this to
226-
/// return `false` and rely on their own `&self` / `&mut self` provenance.
227-
#[doc(hidden)]
228-
#[inline(always)]
229-
fn cpi_requires_borrow_check(&self) -> bool {
230-
true
231-
}
232-
233224
/// v1-compatible alias for the account address.
234225
#[cfg(feature = "compat")]
235226
#[inline(always)]
@@ -255,7 +246,7 @@ pub trait AnchorAccount: Deref<Target = Self::Data> + Sized {
255246
/// it is alive. The handle's `is_writable` flag is `false`.
256247
#[inline(always)]
257248
fn cpi_handle(&self) -> CpiHandle<'_> {
258-
CpiHandle::readonly_with_borrow_check(self.account(), self.cpi_requires_borrow_check())
249+
CpiHandle::readonly(self.account())
259250
}
260251

261252
/// Obtain a writable CPI handle for this account.
@@ -280,10 +271,7 @@ pub trait AnchorAccount: Deref<Target = Self::Data> + Sized {
280271
#[inline(always)]
281272
fn try_cpi_handle_mut(&mut self) -> Result<CpiHandleMut<'_>, ProgramError> {
282273
require!(self.account().is_writable(), ProgramError::InvalidArgument);
283-
Ok(CpiHandleMut::with_borrow_check(
284-
self.account(),
285-
self.cpi_requires_borrow_check(),
286-
))
274+
Ok(CpiHandleMut::from_account(self.account()))
287275
}
288276
}
289277

lang-v2/tests/program_invoke.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,20 @@ fn checked_invoke_rejects_live_borrow_for_writable_meta() {
245245
assert_eq!(err, ProgramError::AccountBorrowFailed);
246246
}
247247

248+
#[test]
249+
fn checked_invoke_rejects_live_mut_borrow_for_writable_meta() {
250+
let buffer = account_view([1; 32], true);
251+
let mut view = unsafe { buffer.view() };
252+
let mut borrow_view = view;
253+
let _borrow = borrow_view.try_borrow_mut().unwrap();
254+
let ix = instruction(*view.address(), true);
255+
let handles = [view.to_cpi_handle_mut().into()];
256+
257+
let err = program::invoke(&ix, &handles).unwrap_err();
258+
259+
assert_eq!(err, ProgramError::AccountBorrowFailed);
260+
}
261+
248262
#[test]
249263
fn checked_invoke_accepts_mutable_slab_handle() {
250264
let buffer = slab_account_view([1; 32], true, 9);

0 commit comments

Comments
 (0)