Skip to content

Commit d710b1e

Browse files
committed
check bool on non-solana target
1 parent 190e542 commit d710b1e

File tree

3 files changed

+106
-7
lines changed

3 files changed

+106
-7
lines changed

sysvar/src/epoch_rewards.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,20 +154,28 @@
154154
//! # Ok::<(), anyhow::Error>(())
155155
//! ```
156156
157+
use crate::Sysvar;
157158
#[cfg(feature = "bincode")]
158159
use crate::SysvarSerialize;
159-
use crate::{impl_sysvar_get, Sysvar};
160160
pub use {
161161
solana_epoch_rewards::EpochRewards,
162162
solana_sdk_ids::sysvar::epoch_rewards::{check_id, id, ID},
163163
};
164164

165+
#[cfg(not(target_os = "solana"))]
166+
impl Sysvar for EpochRewards {
167+
// For non-Solana targets (test environments), validate that the final byte
168+
// is a valid bool (0x00 or 0x01) since data may come from untrusted sources.
169+
crate::impl_sysvar_get_check_final_bool!(id(), 15);
170+
}
171+
172+
#[cfg(target_os = "solana")]
165173
impl Sysvar for EpochRewards {
166174
// SAFETY: upstream invariant: the sysvar data is created exclusively
167175
// by the Solana runtime and serializes bool as 0x00 or 0x01, so the final
168176
// `bool` field of `EpochRewards` can be re-aligned with padding and read
169177
// directly without validation.
170-
impl_sysvar_get!(id(), 15);
178+
crate::impl_sysvar_get!(id(), 15);
171179
}
172180

173181
#[cfg(feature = "bincode")]
@@ -198,4 +206,32 @@ mod tests {
198206
let got = EpochRewards::get().unwrap();
199207
assert_eq!(got, expected);
200208
}
209+
210+
#[test]
211+
#[serial]
212+
#[cfg(feature = "bincode")]
213+
fn test_impl_sysvar_epochrewards_fails_non_solana_bad_bool() {
214+
let epoch_rewards = EpochRewards {
215+
distribution_starting_block_height: 42,
216+
num_partitions: 7,
217+
parent_blockhash: solana_hash::Hash::new_unique(),
218+
total_points: 1234567890,
219+
total_rewards: 100,
220+
distributed_rewards: 10,
221+
active: false,
222+
};
223+
224+
let mut data = bincode::serialize(&epoch_rewards).unwrap();
225+
assert_eq!(data.len(), 81);
226+
227+
// Corrupt the bool byte (last byte of serialized data) to an invalid value
228+
data[80] = 2;
229+
230+
crate::tests::mock_get_sysvar_syscall(&data);
231+
let result = EpochRewards::get();
232+
assert_eq!(
233+
result,
234+
Err(solana_program_error::ProgramError::InvalidAccountData)
235+
);
236+
}
201237
}

sysvar/src/epoch_schedule.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ impl PodEpochSchedule {
171171
}
172172

173173
pub fn warmup(&self) -> bool {
174+
#[cfg(not(target_os = "solana"))]
175+
if self.warmup > 1 {
176+
panic!("Invalid warmup value: {}", self.warmup);
177+
}
178+
174179
// SAFETY: upstream invariant: the sysvar data is created exclusively
175180
// by the Solana runtime and serializes bool as 0x00 or 0x01.
176181
self.warmup > 0

sysvar/src/lib.rs

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,31 @@ pub trait SysvarSerialize:
158158
}
159159
}
160160

161-
/// Implements the [`Sysvar::get`] method for both SBF and host targets.
161+
/// Helper for conditional bool validation. Expands to a check or nothing.
162162
#[macro_export]
163-
macro_rules! impl_sysvar_get {
163+
#[doc(hidden)]
164+
macro_rules! __maybe_check_final_bool {
165+
(true, $var_addr:ident, $length:ident) => {
166+
let bool_byte = *$var_addr.add($length - 1);
167+
if bool_byte > 1 {
168+
return Err($crate::__private::ProgramError::InvalidAccountData);
169+
}
170+
};
171+
(false, $var_addr:ident, $length:ident) => {};
172+
}
173+
174+
/// Internal macro that implements [`Sysvar::get`] with optional bool validation.
175+
///
176+
/// Not intended for direct use. Use [`impl_sysvar_get`] or
177+
/// [`impl_sysvar_get_check_final_bool`] instead.
178+
#[macro_export]
179+
#[doc(hidden)]
180+
macro_rules! __impl_sysvar_get_internal {
164181
// DEPRECATED: This variant is only for the deprecated Fees sysvar and should be
165182
// removed once Fees is no longer in use. It uses the old-style direct syscall
166183
// approach instead of the new sol_get_sysvar syscall.
167-
($syscall_name:ident) => {
184+
// The check_final_bool parameter is ignored for this deprecated path.
185+
($syscall_name:ident, $_check_final_bool:tt) => {
168186
fn get() -> Result<Self, $crate::__private::ProgramError> {
169187
let mut var = Self::default();
170188
let var_addr = &mut var as *mut _ as *mut u8;
@@ -185,7 +203,8 @@ macro_rules! impl_sysvar_get {
185203
// (size - padding bytes) and zeros the padding to avoid undefined behavior.
186204
// Only supports sysvars where padding is at the end of the layout. Caller
187205
// must supply the correct number of padding bytes.
188-
($sysvar_id:expr, $padding:literal) => {
206+
// If check_final_bool is true, validates that the final byte is 0x00 or 0x01.
207+
($sysvar_id:expr, $padding:literal, $check_final_bool:tt) => {
189208
fn get() -> Result<Self, $crate::__private::ProgramError> {
190209
let mut var = core::mem::MaybeUninit::<Self>::uninit();
191210
let var_addr = var.as_mut_ptr() as *mut u8;
@@ -201,6 +220,7 @@ macro_rules! impl_sysvar_get {
201220
// SAFETY: All bytes now initialized: syscall filled data
202221
// bytes and we zeroed padding.
203222
unsafe {
223+
$crate::__maybe_check_final_bool!($check_final_bool, var_addr, length);
204224
var_addr.add(length).write_bytes(0, $padding);
205225
Ok(var.assume_init())
206226
}
@@ -211,8 +231,46 @@ macro_rules! impl_sysvar_get {
211231
}
212232
};
213233
// Variant for sysvars without padding (struct size matches bincode size).
234+
($sysvar_id:expr, $check_final_bool:tt) => {
235+
$crate::__impl_sysvar_get_internal!($sysvar_id, 0, $check_final_bool);
236+
};
237+
}
238+
239+
/// Implements the [`Sysvar::get`] method for both SBF and host targets.
240+
#[macro_export]
241+
macro_rules! impl_sysvar_get {
242+
// DEPRECATED: This variant is only for the deprecated Fees sysvar.
243+
($syscall_name:ident) => {
244+
$crate::__impl_sysvar_get_internal!($syscall_name, false);
245+
};
246+
// Variant for sysvars with padding at the end.
247+
($sysvar_id:expr, $padding:literal) => {
248+
$crate::__impl_sysvar_get_internal!($sysvar_id, $padding, false);
249+
};
250+
// Variant for sysvars without padding.
251+
($sysvar_id:expr) => {
252+
$crate::__impl_sysvar_get_internal!($sysvar_id, 0, false);
253+
};
254+
}
255+
256+
/// Implements the [`Sysvar::get`] method with validation that the final byte is a valid bool.
257+
///
258+
/// This is the same as [`impl_sysvar_get`] but additionally validates that the last byte
259+
/// of the loaded data is either 0x00 or 0x01.
260+
#[cfg(not(target_os = "solana"))]
261+
#[macro_export]
262+
macro_rules! impl_sysvar_get_check_final_bool {
263+
// DEPRECATED: This variant is only for the deprecated Fees sysvar.
264+
($syscall_name:ident) => {
265+
$crate::__impl_sysvar_get_internal!($syscall_name, true);
266+
};
267+
// Variant for sysvars with padding at the end.
268+
($sysvar_id:expr, $padding:literal) => {
269+
$crate::__impl_sysvar_get_internal!($sysvar_id, $padding, true);
270+
};
271+
// Variant for sysvars without padding.
214272
($sysvar_id:expr) => {
215-
$crate::impl_sysvar_get!($sysvar_id, 0);
273+
$crate::__impl_sysvar_get_internal!($sysvar_id, 0, true);
216274
};
217275
}
218276

0 commit comments

Comments
 (0)