1mod helpers;
4
5use helpers::{check_limit, effective_pages, next_generation, report_from_state};
6
7use alloc::vec::Vec;
8use core::{
9 cell::{RefCell, UnsafeCell},
10 marker::PhantomData,
11 sync::atomic::{AtomicUsize, Ordering},
12};
13
14use super::{
15 AttestationEvidence, DisposalResult, JournalDisposition, PhysicalProtection,
16 ProtectedMemoryProvider, ProtectionError, ProtectionRequest, ProviderAccess, ProviderHealth,
17 ProviderLimits, ProviderOperationResult, ProviderReport, QuarantineRecord, ResourceKind,
18 TeardownCursor, WipeConfirmation, WipeEvidence,
19};
20
21static NEXT_PROVIDER_IDENTITY: AtomicUsize = AtomicUsize::new(1);
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23enum SlotLifecycle {
24 Free,
25 Reserved,
26 Active,
27 Quarantined,
28 PermanentlyQuarantined,
29}
30
31struct Slot {
32 generation: usize,
33 lifecycle: SlotLifecycle,
34 logical_bytes: usize,
35 effective_pages: usize,
36 retries: usize,
37 storage: Option<Vec<u8>>,
38 cursor: TeardownCursor,
39}
40
41impl Slot {
42 const fn free() -> Self {
43 Self {
44 generation: 1,
45 lifecycle: SlotLifecycle::Free,
46 logical_bytes: 0,
47 effective_pages: 0,
48 retries: 0,
49 storage: None,
50 cursor: TeardownCursor::new(),
51 }
52 }
53
54 fn reset(&mut self) {
55 self.generation = self.generation.checked_add(1).unwrap_or(0);
56 self.lifecycle = SlotLifecycle::Free;
57 self.logical_bytes = 0;
58 self.effective_pages = 0;
59 self.retries = 0;
60 self.storage = None;
61 self.cursor = TeardownCursor::new();
62 }
63}
64
65struct ProviderState<const SLOTS: usize> {
66 health: ProviderHealth,
67 health_generation: usize,
68 protection_generation: usize,
69 slots: [Slot; SLOTS],
70}
71
72impl<const SLOTS: usize> Drop for ProviderState<SLOTS> {
73 fn drop(&mut self) {
74 for slot in &mut self.slots {
75 if let Some(storage) = slot.storage.as_deref_mut() {
76 crate::wipe_bytes(storage);
77 }
78 }
79 }
80}
81
82#[doc(hidden)]
83pub struct BestEffortReservation {
85 slot: usize,
86 generation: usize,
87 request: ProtectionRequest,
88}
89
90#[doc(hidden)]
91pub struct BestEffortHandle {
93 slot: usize,
94 generation: usize,
95 reserved_pages: usize,
96 bytes: Vec<u8>,
97 _not_thread_or_unwind_safe: PhantomData<(UnsafeCell<()>, &'static mut dyn FnMut())>,
98}
99
100pub struct BestEffortProvider<const SLOTS: usize> {
107 identity: usize,
108 limits: ProviderLimits,
109 state: RefCell<ProviderState<SLOTS>>,
110}
111impl<const SLOTS: usize> BestEffortProvider<SLOTS> {
112 #[allow(deprecated)]
116 pub fn new(limits: ProviderLimits) -> Result<Self, ProtectionError> {
117 if limits.page_size == 0
118 || limits.max_identities > SLOTS
119 || limits.max_registry_entries > SLOTS
120 || limits.max_retry_attempts == 0
121 || limits.max_maintenance_work == 0
122 {
123 return Err(ProtectionError::InvalidLimits);
124 }
125 let identity = NEXT_PROVIDER_IDENTITY
126 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
127 value.checked_add(1)
128 })
129 .map_err(|_| ProtectionError::ProviderUnavailable)?;
130 Ok(Self {
131 identity,
132 limits,
133 state: RefCell::new(ProviderState {
134 health: ProviderHealth::Healthy,
135 health_generation: 1,
136 protection_generation: 1,
137 slots: core::array::from_fn(|_| Slot::free()),
138 }),
139 })
140 }
141
142 pub fn maintain(&self) -> usize {
148 let mut state = self.state.borrow_mut();
149 let mut work = 0;
150 let mut shutdown = false;
151 for slot in &mut state.slots {
152 if work >= self.limits.max_maintenance_work {
153 break;
154 }
155 if slot.lifecycle != SlotLifecycle::Quarantined {
156 continue;
157 }
158 work += 1;
159 if slot.retries >= self.limits.max_retry_attempts {
160 slot.lifecycle = SlotLifecycle::PermanentlyQuarantined;
161 shutdown = true;
162 continue;
163 }
164 slot.retries += 1;
165 if let Some(storage) = slot.storage.as_deref_mut() {
166 crate::wipe_bytes(storage);
167 }
168 slot.storage = None;
169 slot.reset();
170 }
171 if shutdown {
172 state.health = ProviderHealth::Shutdown;
173 state.health_generation = next_generation(state.health_generation);
174 }
175 work
176 }
177
178 pub fn restore_health_after_self_check(&self) -> Result<(), ProtectionError> {
180 let mut state = self.state.borrow_mut();
181 if state.slots.iter().any(|slot| {
182 matches!(
183 slot.lifecycle,
184 SlotLifecycle::Quarantined | SlotLifecycle::PermanentlyQuarantined
185 )
186 }) {
187 return Err(ProtectionError::ProviderUnavailable);
188 }
189 let health_generation = next_generation(state.health_generation);
190 let protection_generation = next_generation(state.protection_generation);
191 if health_generation == 0 || protection_generation == 0 {
192 state.health = ProviderHealth::Shutdown;
193 state.health_generation = health_generation;
194 state.protection_generation = protection_generation;
195 return Err(ProtectionError::ProviderUnavailable);
196 }
197 state.health = ProviderHealth::Healthy;
198 state.health_generation = health_generation;
199 state.protection_generation = protection_generation;
200 Ok(())
201 }
202
203 fn release_reservation(&self, reservation: &BestEffortReservation) {
204 let mut state = self.state.borrow_mut();
205 if let Some(slot) = state.slots.get_mut(reservation.slot)
206 && slot.generation == reservation.generation
207 && slot.lifecycle == SlotLifecycle::Reserved
208 {
209 slot.reset();
210 }
211 }
212}
213
214#[allow(unsafe_code)]
215unsafe impl<const SLOTS: usize> ProtectedMemoryProvider for BestEffortProvider<SLOTS> {
216 type Handle = BestEffortHandle;
217 type Reservation = BestEffortReservation;
218
219 fn provider_identity(&self) -> usize {
220 self.identity
221 }
222 fn provider_generation(&self) -> usize {
223 1
224 }
225
226 fn health_generation(&self) -> usize {
227 self.state.borrow().health_generation
228 }
229
230 fn protection_generation(&self) -> usize {
231 self.state.borrow().protection_generation
232 }
233
234 fn health(&self) -> ProviderHealth {
235 self.state.borrow().health
236 }
237
238 fn limits(&self) -> ProviderLimits {
239 self.limits
240 }
241
242 fn report(&self) -> ProviderReport {
243 let state = self.state.borrow();
244 let mut report = ProviderReport {
245 health: state.health,
246 health_generation: state.health_generation,
247 protection_generation: state.protection_generation,
248 active_and_reserved: 0,
249 quarantined: 0,
250 permanently_quarantined: 0,
251 tombstoned: 0,
252 charged_logical_bytes: 0,
253 charged_effective_pages: 0,
254 };
255 for slot in &state.slots {
256 match slot.lifecycle {
257 SlotLifecycle::Reserved | SlotLifecycle::Active => report.active_and_reserved += 1,
258 SlotLifecycle::Quarantined => report.quarantined += 1,
259 SlotLifecycle::PermanentlyQuarantined => {
260 report.permanently_quarantined += 1;
261 }
262 SlotLifecycle::Free => continue,
263 }
264 report.charged_logical_bytes += slot.logical_bytes;
265 report.charged_effective_pages += slot.effective_pages;
266 }
267 report
268 }
269
270 fn reserve(
271 &self,
272 _access: &ProviderAccess,
273 request: ProtectionRequest,
274 ) -> Result<Self::Reservation, ProtectionError> {
275 if request.requires_attestation() {
276 return Err(ProtectionError::ProtectionUnavailable);
277 }
278 let mut state = self.state.borrow_mut();
279 if state.health != ProviderHealth::Healthy {
280 return Err(ProtectionError::ProviderUnavailable);
281 }
282 let report = report_from_state(&state);
283 let used_identities = report
284 .active_and_reserved
285 .checked_add(report.quarantined)
286 .and_then(|used| used.checked_add(report.permanently_quarantined))
287 .and_then(|used| used.checked_add(report.tombstoned))
288 .ok_or(ProtectionError::ProtectionResourceExhausted(
289 ResourceKind::Identities,
290 ))?;
291 let limit_result = check_limit(
292 used_identities,
293 1,
294 self.limits.max_identities,
295 ResourceKind::Identities,
296 )
297 .and_then(|()| {
298 check_limit(
299 used_identities,
300 1,
301 self.limits.max_registry_entries,
302 ResourceKind::RegistryEntries,
303 )
304 })
305 .and_then(|()| {
306 check_limit(
307 report.charged_logical_bytes,
308 request.logical_bytes(),
309 self.limits.max_logical_bytes,
310 ResourceKind::LogicalBytes,
311 )
312 })
313 .and_then(|()| {
314 check_limit(
315 report.charged_effective_pages,
316 request.reserved_pages(),
317 self.limits.max_effective_pages,
318 ResourceKind::EffectivePages,
319 )
320 });
321 if let Err(error) = limit_result {
322 state.health = ProviderHealth::Exhausted;
323 state.health_generation = next_generation(state.health_generation);
324 return Err(error);
325 }
326 let Some((slot_index, slot)) = state
327 .slots
328 .iter_mut()
329 .enumerate()
330 .find(|(_, slot)| slot.lifecycle == SlotLifecycle::Free && slot.generation != 0)
331 else {
332 state.health = ProviderHealth::Exhausted;
333 state.health_generation = next_generation(state.health_generation);
334 return Err(ProtectionError::ProtectionResourceExhausted(
335 ResourceKind::RegistryEntries,
336 ));
337 };
338 slot.lifecycle = SlotLifecycle::Reserved;
339 slot.logical_bytes = request.logical_bytes();
340 slot.effective_pages = request.reserved_pages();
341 Ok(BestEffortReservation {
342 slot: slot_index,
343 generation: slot.generation,
344 request,
345 })
346 }
347
348 fn materialize(
349 &self,
350 _access: &ProviderAccess,
351 reservation: Self::Reservation,
352 ) -> Result<Self::Handle, ProtectionError> {
353 let mut bytes = Vec::new();
354 if bytes
355 .try_reserve_exact(reservation.request.logical_bytes())
356 .is_err()
357 {
358 self.release_reservation(&reservation);
359 return Err(ProtectionError::ProviderUnavailable);
360 }
361 bytes.resize(reservation.request.logical_bytes(), 0);
362 let actual_pages =
363 effective_pages(bytes.as_ptr() as usize, bytes.len(), self.limits.page_size)?;
364 if actual_pages > reservation.request.reserved_pages() {
365 crate::wipe_bytes(&mut bytes);
366 self.release_reservation(&reservation);
367 return Err(ProtectionError::ActualRangeExceededReservation);
368 }
369 let mut state = self.state.borrow_mut();
370 let Some(slot) = state.slots.get_mut(reservation.slot) else {
371 crate::wipe_bytes(&mut bytes);
372 return Err(ProtectionError::ProviderUnavailable);
373 };
374 if slot.generation != reservation.generation || slot.lifecycle != SlotLifecycle::Reserved {
375 crate::wipe_bytes(&mut bytes);
376 return Err(ProtectionError::ProviderUnavailable);
377 }
378 slot.lifecycle = SlotLifecycle::Active;
379 slot.effective_pages = actual_pages;
380 Ok(BestEffortHandle {
381 slot: reservation.slot,
382 generation: reservation.generation,
383 reserved_pages: actual_pages,
384 bytes,
385 _not_thread_or_unwind_safe: PhantomData,
386 })
387 }
388
389 fn logical_len(&self, _access: &ProviderAccess, handle: &Self::Handle) -> usize {
390 handle.bytes.len()
391 }
392
393 fn physical_protection(
394 &self,
395 _access: &ProviderAccess,
396 _handle: &Self::Handle,
397 ) -> PhysicalProtection {
398 PhysicalProtection::ProtectionConfirmedAbsent
399 }
400
401 fn bytes<'handle>(
402 &self,
403 _access: &ProviderAccess,
404 handle: &'handle Self::Handle,
405 ) -> &'handle [u8] {
406 &handle.bytes
407 }
408
409 fn bytes_mut<'handle>(
410 &self,
411 _access: &ProviderAccess,
412 handle: &'handle mut Self::Handle,
413 ) -> &'handle mut [u8] {
414 &mut handle.bytes
415 }
416
417 fn confirm_wipe(
418 &self,
419 _access: &ProviderAccess,
420 _handle: &Self::Handle,
421 _attestation: Option<AttestationEvidence>,
422 cursor: &mut TeardownCursor,
423 ) -> WipeConfirmation {
424 cursor.disposition = JournalDisposition::Applied;
425 WipeConfirmation {
426 result: ProviderOperationResult::Applied,
427 evidence: WipeEvidence::WipedBestEffort,
428 }
429 }
430
431 fn remove_protection(
432 &self,
433 _access: &ProviderAccess,
434 _handle: &mut Self::Handle,
435 cursor: &mut TeardownCursor,
436 ) -> ProviderOperationResult {
437 cursor.disposition = JournalDisposition::Applied;
438 ProviderOperationResult::Applied
439 }
440
441 fn reconcile_accounting(
442 &self,
443 _access: &ProviderAccess,
444 _handle: &mut Self::Handle,
445 cursor: &mut TeardownCursor,
446 ) -> ProviderOperationResult {
447 cursor.disposition = JournalDisposition::Applied;
448 ProviderOperationResult::Applied
449 }
450
451 fn dispose(
452 &self,
453 _access: &ProviderAccess,
454 mut handle: Self::Handle,
455 cursor: &mut TeardownCursor,
456 ) -> DisposalResult<Self::Handle> {
457 cursor.disposition = JournalDisposition::Applied;
458 crate::wipe_bytes(&mut handle.bytes);
459 let mut state = self.state.borrow_mut();
460 if let Some(slot) = state.slots.get_mut(handle.slot)
461 && slot.generation == handle.generation
462 && slot.lifecycle == SlotLifecycle::Active
463 {
464 slot.reset();
465 }
466 DisposalResult::Applied
467 }
468
469 fn quarantine(&self, _access: &ProviderAccess, handle: Self::Handle, record: QuarantineRecord) {
470 let mut state = self.state.borrow_mut();
471 state.health = ProviderHealth::Degraded;
472 state.health_generation = next_generation(state.health_generation);
473 if let Some(slot) = state.slots.get_mut(handle.slot)
474 && slot.generation == handle.generation
475 {
476 slot.lifecycle = SlotLifecycle::Quarantined;
477 slot.retries = record.retry_attempt;
478 slot.cursor = record.cursor;
479 slot.logical_bytes = handle.bytes.len();
480 slot.effective_pages = handle.reserved_pages;
481 slot.storage = Some(handle.bytes);
482 }
483 }
484}