1#[cfg(feature = "nightly")]
151use std::alloc::{AllocError, Allocator};
152use std::fmt;
153use std::marker::PhantomData;
154use std::ptr::{self, NonNull};
155use std::sync::LazyLock;
156
157use subtle::ConstantTimeEq;
158use zeroize::{Zeroize, ZeroizeOnDrop};
159
160use crate::error;
161use crate::rng::copy_randombytes;
162pub use crate::types::*;
163
164mod int {
165 #[derive(Clone, Debug, PartialEq, Eq)]
166 pub(super) enum LockMode {
167 Locked,
168 Unlocked,
169 }
170
171 #[derive(Clone, Debug, PartialEq, Eq)]
172 pub(super) enum ProtectMode {
173 ReadOnly,
174 ReadWrite,
175 NoAccess,
176 }
177
178 #[derive(Clone)]
179 pub(super) struct InternalData<A> {
180 pub(super) a: A,
181 pub(super) lm: LockMode,
182 pub(super) pm: ProtectMode,
183 }
184}
185
186#[doc(hidden)] pub mod traits {
188 pub trait ProtectMode {}
189 pub struct ReadOnly {}
190 pub struct ReadWrite {}
191 pub struct NoAccess {}
192
193 impl ProtectMode for ReadOnly {}
194 impl ProtectMode for ReadWrite {}
195 impl ProtectMode for NoAccess {}
196
197 pub trait LockMode {}
198 pub struct Locked {}
199 pub struct Unlocked {}
200 impl LockMode for Locked {}
201 impl LockMode for Unlocked {}
202}
203
204pub trait Lockable<A: Zeroize + Bytes> {
207 fn mlock(self) -> Result<Protected<A, traits::ReadWrite, traits::Locked>, error::Error>;
219}
220
221pub trait Lock<A: Zeroize + Bytes, PM: traits::ProtectMode> {
223 fn mlock(self) -> Result<Protected<A, PM, traits::Locked>, error::Error>;
233}
234
235pub trait Unlock<A: Zeroize + Bytes, PM: traits::ProtectMode> {
237 fn munlock(self) -> Result<Protected<A, PM, traits::Unlocked>, error::Error>;
244}
245
246pub trait ProtectReadOnly<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> {
248 fn mprotect_readonly(self) -> Result<Protected<A, traits::ReadOnly, LM>, error::Error>;
255}
256
257pub trait ProtectReadWrite<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> {
259 fn mprotect_readwrite(self) -> Result<Protected<A, traits::ReadWrite, LM>, error::Error>;
266}
267
268pub trait ProtectNoAccess<A: Zeroize + Bytes, PM: traits::ProtectMode> {
270 fn mprotect_noaccess(
277 self,
278 ) -> Result<Protected<A, traits::NoAccess, traits::Unlocked>, error::Error>;
279}
280
281pub trait NewLocked<A: Zeroize + NewBytes + Lockable<A>> {
283 fn new_locked() -> Result<Protected<A, traits::ReadWrite, traits::Locked>, error::Error>;
290 fn new_readonly_locked() -> Result<Protected<A, traits::ReadOnly, traits::Locked>, error::Error>;
297 fn generate_locked() -> Result<Protected<A, traits::ReadWrite, traits::Locked>, error::Error>;
303 fn generate_readonly_locked()
310 -> Result<Protected<A, traits::ReadOnly, traits::Locked>, error::Error>;
311 #[deprecated(note = "use generate_locked() instead")]
320 fn gen_locked() -> Result<Protected<A, traits::ReadWrite, traits::Locked>, error::Error> {
321 Self::generate_locked()
322 }
323 #[deprecated(note = "use generate_readonly_locked() instead")]
333 fn gen_readonly_locked() -> Result<Protected<A, traits::ReadOnly, traits::Locked>, error::Error>
334 {
335 Self::generate_readonly_locked()
336 }
337}
338
339pub trait NewLockedFromSlice<A: Zeroize + NewBytes + Lockable<A>> {
341 fn from_slice_into_locked(
353 src: &[u8],
354 ) -> Result<Protected<A, traits::ReadWrite, traits::Locked>, crate::error::Error>;
355 fn from_slice_into_readonly_locked(
368 src: &[u8],
369 ) -> Result<Protected<A, traits::ReadOnly, traits::Locked>, crate::error::Error>;
370}
371
372pub struct Protected<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> {
376 i: Option<int::InternalData<A>>,
377 p: PhantomData<PM>,
378 l: PhantomData<LM>,
379}
380
381pub mod ptypes {
383 pub type Locked<T> = super::Protected<T, super::traits::ReadWrite, super::traits::Locked>;
385 pub type LockedRO<T> = super::Protected<T, super::traits::ReadOnly, super::traits::Locked>;
387 pub type NoAccess<T> = super::Protected<T, super::traits::NoAccess, super::traits::Unlocked>;
389 pub type Unlocked<T> = super::Protected<T, super::traits::ReadWrite, super::traits::Unlocked>;
391 pub type UnlockedRO<T> = super::Protected<T, super::traits::ReadOnly, super::traits::Unlocked>;
393 pub type LockedBytes = Locked<super::HeapBytes>;
395}
396
397impl<T: Zeroize + NewBytes + ResizableBytes + Lockable<T> + NewLocked<T>> Clone for Locked<T> {
398 fn clone(&self) -> Self {
399 let mut cloned = T::new_locked().expect("unable to create new locked instance");
400 cloned.resize(self.len(), 0);
401 cloned.as_mut_slice().copy_from_slice(self.as_slice());
402 cloned
403 }
404}
405
406impl<T: Zeroize + NewBytes + ResizableBytes + Lockable<T> + NewLocked<T>> Clone for LockedRO<T> {
407 fn clone(&self) -> Self {
408 let mut cloned = T::new_locked().expect("unable to create new locked instance");
409 cloned.resize(self.len(), 0);
410 cloned.as_mut_slice().copy_from_slice(self.as_slice());
411 cloned
412 .mprotect_readonly()
413 .expect("unable to protect readonly")
414 }
415}
416
417impl<T: Zeroize + Bytes + Clone> Clone for Unlocked<T> {
418 fn clone(&self) -> Self {
419 Self::new_with(self.i.as_ref().unwrap().a.clone())
420 }
421}
422
423impl<T: Zeroize + NewBytes + Clone> Clone for UnlockedRO<T> {
424 fn clone(&self) -> Self {
425 Unlocked::<T>::new_with(self.i.as_ref().unwrap().a.clone())
426 .mprotect_readonly()
427 .expect("unable to create new readonly instance")
428 }
429}
430
431pub use ptypes::*;
432
433fn dryoc_mlock(data: &[u8]) -> Result<(), std::io::Error> {
434 if data.is_empty() {
435 return Ok(());
437 }
438 #[cfg(unix)]
439 {
440 #[cfg(target_os = "linux")]
441 {
442 use libc::{MADV_DONTDUMP, madvise};
444 unsafe {
448 madvise(data.as_ptr() as *mut c_void, data.len(), MADV_DONTDUMP);
449 }
450 }
451
452 use libc::{c_void, mlock as c_mlock};
453 let ret = unsafe { c_mlock(data.as_ptr() as *const c_void, data.len()) };
456 match ret {
457 0 => Ok(()),
458 _ => Err(std::io::Error::last_os_error()),
459 }
460 }
461 #[cfg(windows)]
462 {
463 use winapi::shared::minwindef::LPVOID;
464 use winapi::um::memoryapi::VirtualLock;
465
466 let res = unsafe { VirtualLock(data.as_ptr() as LPVOID, data.len()) };
469 if res != 0 {
470 Ok(())
471 } else {
472 Err(std::io::Error::last_os_error())
473 }
474 }
475}
476
477fn dryoc_munlock(data: &[u8]) -> Result<(), std::io::Error> {
478 if data.is_empty() {
479 return Ok(());
481 }
482 #[cfg(unix)]
483 {
484 #[cfg(target_os = "linux")]
485 {
486 use libc::{MADV_DODUMP, madvise};
488 unsafe {
491 madvise(data.as_ptr() as *mut c_void, data.len(), MADV_DODUMP);
492 }
493 }
494
495 use libc::{c_void, munlock as c_munlock};
496 let ret = unsafe { c_munlock(data.as_ptr() as *const c_void, data.len()) };
499 match ret {
500 0 => Ok(()),
501 _ => Err(std::io::Error::last_os_error()),
502 }
503 }
504 #[cfg(windows)]
505 {
506 use winapi::shared::minwindef::LPVOID;
507 use winapi::um::memoryapi::VirtualUnlock;
508
509 let res = unsafe { VirtualUnlock(data.as_ptr() as LPVOID, data.len()) };
512 if res != 0 {
513 Ok(())
514 } else {
515 Err(std::io::Error::last_os_error())
516 }
517 }
518}
519
520fn dryoc_mprotect_readonly(data: &[u8]) -> Result<(), std::io::Error> {
521 dryoc_mprotect_ptr(
522 data.as_ptr() as *mut u8,
523 data.len(),
524 PageProtectMode::ReadOnly,
525 )
526}
527
528fn dryoc_mprotect_readwrite(data: &[u8]) -> Result<(), std::io::Error> {
529 dryoc_mprotect_ptr(
530 data.as_ptr() as *mut u8,
531 data.len(),
532 PageProtectMode::ReadWrite,
533 )
534}
535
536fn dryoc_mprotect_readwrite_ptr(data: *mut u8, len: usize) -> Result<(), std::io::Error> {
537 dryoc_mprotect_ptr(data, len, PageProtectMode::ReadWrite)
538}
539
540fn dryoc_mprotect_noaccess(data: &[u8]) -> Result<(), std::io::Error> {
541 dryoc_mprotect_ptr(
542 data.as_ptr() as *mut u8,
543 data.len(),
544 PageProtectMode::NoAccess,
545 )
546}
547
548fn dryoc_mprotect_mode(data: &[u8], mode: &int::ProtectMode) -> Result<(), std::io::Error> {
549 match mode {
550 int::ProtectMode::ReadOnly => dryoc_mprotect_readonly(data),
551 int::ProtectMode::ReadWrite => dryoc_mprotect_readwrite(data),
552 int::ProtectMode::NoAccess => dryoc_mprotect_noaccess(data),
553 }
554}
555
556fn dryoc_mprotect_noaccess_ptr(data: *mut u8, len: usize) -> Result<(), std::io::Error> {
557 dryoc_mprotect_ptr(data, len, PageProtectMode::NoAccess)
558}
559
560#[derive(Clone, Copy)]
561enum PageProtectMode {
562 ReadOnly,
563 ReadWrite,
564 NoAccess,
565}
566
567fn dryoc_mprotect_ptr(
568 data: *mut u8,
569 len: usize,
570 mode: PageProtectMode,
571) -> Result<(), std::io::Error> {
572 if len == 0 {
573 return Ok(());
575 }
576 #[cfg(unix)]
577 {
578 use libc::{PROT_NONE, PROT_READ, PROT_WRITE, c_void, mprotect as c_mprotect};
579 let prot = match mode {
580 PageProtectMode::ReadOnly => PROT_READ,
581 PageProtectMode::ReadWrite => PROT_READ | PROT_WRITE,
582 PageProtectMode::NoAccess => PROT_NONE,
583 };
584 let ret = unsafe { c_mprotect(data as *mut c_void, len, prot) };
587 match ret {
588 0 => Ok(()),
589 _ => Err(std::io::Error::last_os_error()),
590 }
591 }
592 #[cfg(windows)]
593 {
594 use winapi::shared::minwindef::{DWORD, LPVOID};
595 use winapi::um::memoryapi::VirtualProtect;
596 use winapi::um::winnt::{PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE};
597
598 let protect = match mode {
599 PageProtectMode::ReadOnly => PAGE_READONLY,
600 PageProtectMode::ReadWrite => PAGE_READWRITE,
601 PageProtectMode::NoAccess => PAGE_NOACCESS,
602 };
603 let mut old: DWORD = 0;
604
605 let res = unsafe { VirtualProtect(data as LPVOID, len, protect, &mut old) };
609 if res != 0 {
610 Ok(())
611 } else {
612 Err(std::io::Error::last_os_error())
613 }
614 }
615}
616
617impl<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> Protected<A, PM, LM> {
618 fn new() -> Self {
619 Self {
620 i: None,
621 p: PhantomData,
622 l: PhantomData,
623 }
624 }
625
626 fn new_with(a: A) -> Self {
627 Self {
628 i: Some(int::InternalData {
629 a,
630 lm: int::LockMode::Unlocked,
631 pm: int::ProtectMode::ReadWrite,
632 }),
633 p: PhantomData,
634 l: PhantomData,
635 }
636 }
637
638 fn swap_some_or_err<F, OPM: traits::ProtectMode, OLM: traits::LockMode>(
639 &mut self,
640 f: F,
641 ) -> Result<Protected<A, OPM, OLM>, error::Error>
642 where
643 F: Fn(&mut int::InternalData<A>) -> Result<Protected<A, OPM, OLM>, error::Error>,
644 {
645 match &mut self.i {
646 Some(d) => {
647 let mut new = f(d)?;
648 std::mem::swap(&mut new.i, &mut self.i);
650 Ok(new)
651 }
652 _ => Err(error::Error::invalid_state(
653 crate::ErrorContext::ProtectedMemory,
654 )),
655 }
656 }
657}
658
659impl<A: Zeroize + Bytes, PM: traits::ProtectMode> Unlock<A, PM>
660 for Protected<A, PM, traits::Locked>
661{
662 fn munlock(mut self) -> Result<Protected<A, PM, traits::Unlocked>, error::Error> {
663 self.swap_some_or_err(|old| {
664 dryoc_munlock(old.a.as_slice())?;
665 old.lm = int::LockMode::Unlocked;
667 Ok(Protected::<A, PM, traits::Unlocked>::new())
668 })
669 }
670}
671
672impl<A: Zeroize + Bytes + Default, PM: traits::ProtectMode> Lock<A, PM>
673 for Protected<A, PM, traits::Unlocked>
674{
675 fn mlock(mut self) -> Result<Protected<A, PM, traits::Locked>, error::Error> {
676 self.swap_some_or_err(|old| {
677 dryoc_mlock(old.a.as_slice())?;
678 old.lm = int::LockMode::Locked;
680 Ok(Protected::<A, PM, traits::Locked>::new())
681 })
682 }
683}
684
685impl<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> ProtectReadOnly<A, PM, LM>
686 for Protected<A, PM, LM>
687{
688 fn mprotect_readonly(mut self) -> Result<Protected<A, traits::ReadOnly, LM>, error::Error> {
689 self.swap_some_or_err(|old| {
690 dryoc_mprotect_readonly(old.a.as_slice())?;
691 old.pm = int::ProtectMode::ReadOnly;
693 Ok(Protected::<A, traits::ReadOnly, LM>::new())
694 })
695 }
696}
697
698impl<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> ProtectReadWrite<A, PM, LM>
699 for Protected<A, PM, LM>
700{
701 fn mprotect_readwrite(mut self) -> Result<Protected<A, traits::ReadWrite, LM>, error::Error> {
702 self.swap_some_or_err(|old| {
703 dryoc_mprotect_readwrite(old.a.as_slice())?;
704 old.pm = int::ProtectMode::ReadWrite;
706 Ok(Protected::<A, traits::ReadWrite, LM>::new())
707 })
708 }
709}
710
711impl<A: Zeroize + Bytes, PM: traits::ProtectMode> ProtectNoAccess<A, PM>
712 for Protected<A, PM, traits::Unlocked>
713{
714 fn mprotect_noaccess(
715 mut self,
716 ) -> Result<Protected<A, traits::NoAccess, traits::Unlocked>, error::Error> {
717 self.swap_some_or_err(|old| {
718 dryoc_mprotect_noaccess(old.a.as_slice())?;
719 old.pm = int::ProtectMode::NoAccess;
721 Ok(Protected::<A, traits::NoAccess, traits::Unlocked>::new())
722 })
723 }
724}
725
726impl<A: Zeroize + Bytes + AsRef<[u8]>, LM: traits::LockMode> AsRef<[u8]>
727 for Protected<A, traits::ReadOnly, LM>
728{
729 fn as_ref(&self) -> &[u8] {
730 self.i.as_ref().unwrap().a.as_ref()
731 }
732}
733
734impl<A: Zeroize + Bytes + AsRef<[u8]>, LM: traits::LockMode> AsRef<[u8]>
735 for Protected<A, traits::ReadWrite, LM>
736{
737 fn as_ref(&self) -> &[u8] {
738 self.i.as_ref().unwrap().a.as_ref()
739 }
740}
741
742impl<A: Zeroize + MutBytes + AsMut<[u8]>, LM: traits::LockMode> AsMut<[u8]>
743 for Protected<A, traits::ReadWrite, LM>
744{
745 fn as_mut(&mut self) -> &mut [u8] {
746 self.i.as_mut().unwrap().a.as_mut()
747 }
748}
749
750impl<A: Zeroize + Bytes, LM: traits::LockMode> Bytes for Protected<A, traits::ReadOnly, LM> {
751 #[inline]
752 fn as_slice(&self) -> &[u8] {
753 self.i.as_ref().unwrap().a.as_slice()
754 }
755
756 #[inline]
757 fn len(&self) -> usize {
758 self.i.as_ref().unwrap().a.len()
759 }
760
761 #[inline]
762 fn is_empty(&self) -> bool {
763 self.i.as_ref().unwrap().a.is_empty()
764 }
765}
766
767impl<A: Zeroize + Bytes, LM: traits::LockMode> Bytes for Protected<A, traits::ReadWrite, LM> {
768 #[inline]
769 fn as_slice(&self) -> &[u8] {
770 self.i.as_ref().unwrap().a.as_slice()
771 }
772
773 #[inline]
774 fn len(&self) -> usize {
775 self.i.as_ref().unwrap().a.len()
776 }
777
778 #[inline]
779 fn is_empty(&self) -> bool {
780 self.i.as_ref().unwrap().a.is_empty()
781 }
782}
783
784impl<const LENGTH: usize> From<StackByteArray<LENGTH>> for HeapByteArray<LENGTH> {
785 fn from(other: StackByteArray<LENGTH>) -> Self {
786 let mut r = HeapByteArray::<LENGTH>::new_byte_array();
787 let mut s = other;
788 r.copy_from_slice(s.as_slice());
789 s.zeroize();
790 r
791 }
792}
793
794impl<const LENGTH: usize> StackByteArray<LENGTH> {
795 pub fn mlock(
807 self,
808 ) -> Result<Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Locked>, error::Error>
809 {
810 Protected::<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Unlocked>::new_with(
811 self.into(),
812 )
813 .mlock()
814 }
815}
816
817impl<const LENGTH: usize> StackByteArray<LENGTH> {
818 pub fn mprotect_readonly(
830 self,
831 ) -> Result<Protected<HeapByteArray<LENGTH>, traits::ReadOnly, traits::Unlocked>, error::Error>
832 {
833 Protected::<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Unlocked>::new_with(
834 self.into(),
835 )
836 .mprotect_readonly()
837 }
838}
839
840impl<const LENGTH: usize> Lockable<HeapByteArray<LENGTH>> for HeapByteArray<LENGTH> {
841 fn mlock(
843 self,
844 ) -> Result<Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Locked>, error::Error>
845 {
846 Protected::<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Unlocked>::new_with(self)
847 .mlock()
848 }
849}
850
851impl Lockable<HeapBytes> for HeapBytes {
852 fn mlock(
854 self,
855 ) -> Result<Protected<HeapBytes, traits::ReadWrite, traits::Locked>, error::Error> {
856 Protected::<HeapBytes, traits::ReadWrite, traits::Unlocked>::new_with(self).mlock()
857 }
858}
859
860#[derive(Clone)]
861pub struct PageAlignedAllocator;
866
867#[cfg(unix)]
868const DEFAULT_PAGESIZE: usize = 4096;
869
870#[cfg(unix)]
871fn page_size_from_sysconf(page_size: libc::c_long) -> usize {
872 if page_size > 0 {
873 page_size as usize
874 } else {
875 DEFAULT_PAGESIZE
876 }
877}
878
879static PAGESIZE: LazyLock<usize> = LazyLock::new(|| {
880 #[cfg(unix)]
881 {
882 use libc::{_SC_PAGE_SIZE, sysconf};
883 let page_size = unsafe { sysconf(_SC_PAGE_SIZE) };
886 page_size_from_sysconf(page_size)
887 }
888 #[cfg(windows)]
889 {
890 use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO};
891 let mut si = SYSTEM_INFO::default();
892 unsafe { GetSystemInfo(&mut si) };
895 si.dwPageSize as usize
896 }
897});
898
899fn _page_round(size: usize, pagesize: usize) -> Option<usize> {
900 let rem = size % pagesize;
901 if rem == 0 {
902 Some(size)
903 } else {
904 size.checked_add(pagesize - rem)
905 }
906}
907
908fn protected_alloc_error() -> std::io::Error {
909 std::io::Error::other("protected memory allocation failed")
910}
911
912#[derive(Clone, Copy)]
913struct RawRegionLayout {
914 rounded_size: usize,
915 total_size: usize,
916}
917
918fn checked_raw_region_layout(
919 user_size: usize,
920 pagesize: usize,
921) -> Result<RawRegionLayout, std::io::Error> {
922 let rounded_size = _page_round(user_size, pagesize).ok_or_else(protected_alloc_error)?;
923 let guard_size = pagesize.checked_mul(2).ok_or_else(protected_alloc_error)?;
924 let total_size = rounded_size
925 .checked_add(guard_size)
926 .ok_or_else(protected_alloc_error)?;
927 Ok(RawRegionLayout {
928 rounded_size,
929 total_size,
930 })
931}
932
933#[derive(Clone, Copy)]
934struct RawProtectedAllocation {
935 base: NonNull<u8>,
936 data: NonNull<u8>,
937 rounded_size: usize,
938 total_size: usize,
939}
940
941fn platform_alloc(total_size: usize, pagesize: usize) -> Result<NonNull<u8>, std::io::Error> {
942 #[cfg(unix)]
943 {
944 use libc::posix_memalign;
945 let mut out = ptr::null_mut();
946
947 let ret = unsafe { posix_memalign(&mut out, pagesize, total_size) };
951 if ret != 0 {
952 return Err(std::io::Error::from_raw_os_error(ret));
953 }
954
955 NonNull::new(out as *mut u8).ok_or_else(protected_alloc_error)
956 }
957 #[cfg(windows)]
958 {
959 let _ = pagesize;
960 use winapi::um::memoryapi::VirtualAlloc;
961 use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE};
962
963 let out = unsafe {
967 VirtualAlloc(
968 ptr::null_mut(),
969 total_size,
970 MEM_COMMIT | MEM_RESERVE,
971 PAGE_READWRITE,
972 )
973 };
974
975 NonNull::new(out as *mut u8).ok_or_else(std::io::Error::last_os_error)
976 }
977}
978
979fn platform_free(base: NonNull<u8>, total_size: usize) {
980 #[cfg(unix)]
981 {
982 let _ = total_size;
983 unsafe { libc::free(base.as_ptr() as *mut libc::c_void) };
986 }
987 #[cfg(windows)]
988 {
989 let _ = total_size;
990 use winapi::shared::minwindef::LPVOID;
991 use winapi::um::memoryapi::VirtualFree;
992 use winapi::um::winnt::MEM_RELEASE;
993 unsafe { VirtualFree(base.as_ptr() as LPVOID, 0, MEM_RELEASE) };
996 }
997}
998
999fn allocate_raw_region(user_size: usize) -> Result<RawProtectedAllocation, std::io::Error> {
1000 let pagesize = *PAGESIZE;
1001 let layout = checked_raw_region_layout(user_size, pagesize)?;
1002 let base = platform_alloc(layout.total_size, pagesize)?;
1003 let base_ptr = base.as_ptr();
1004
1005 if let Err(err) = dryoc_mprotect_noaccess_ptr(base_ptr, pagesize) {
1006 platform_free(base, layout.total_size);
1007 return Err(err);
1008 }
1009
1010 let aft_guard_offset = pagesize
1011 .checked_add(layout.rounded_size)
1012 .ok_or_else(protected_alloc_error)?;
1013 let aft_guard = unsafe { base_ptr.add(aft_guard_offset) };
1016 if let Err(err) = dryoc_mprotect_noaccess_ptr(aft_guard, pagesize) {
1017 let _ = dryoc_mprotect_readwrite_ptr(base_ptr, pagesize);
1018 platform_free(base, layout.total_size);
1019 return Err(err);
1020 }
1021
1022 let data_ptr = unsafe { base_ptr.add(pagesize) };
1025 let data = NonNull::new(data_ptr).ok_or_else(protected_alloc_error)?;
1026
1027 Ok(RawProtectedAllocation {
1028 base,
1029 data,
1030 rounded_size: layout.rounded_size,
1031 total_size: layout.total_size,
1032 })
1033}
1034
1035fn deallocate_raw_region(raw: RawProtectedAllocation) {
1036 let pagesize = *PAGESIZE;
1037 let base_ptr = raw.base.as_ptr();
1038 let _ = dryoc_mprotect_readwrite_ptr(base_ptr, pagesize);
1039
1040 if let Some(aft_guard_offset) = pagesize.checked_add(raw.rounded_size) {
1041 let aft_guard = unsafe { base_ptr.add(aft_guard_offset) };
1044 let _ = dryoc_mprotect_readwrite_ptr(aft_guard, pagesize);
1045 }
1046
1047 platform_free(raw.base, raw.total_size);
1048}
1049
1050struct ProtectedBuffer {
1051 base: Option<NonNull<u8>>,
1052 data: NonNull<u8>,
1053 len: usize,
1054 capacity: usize,
1055 rounded_size: usize,
1056 total_size: usize,
1057}
1058
1059unsafe impl Send for ProtectedBuffer {}
1063
1064unsafe impl Sync for ProtectedBuffer {}
1067
1068impl ProtectedBuffer {
1069 fn new_filled(len: usize, value: u8) -> Result<Self, std::io::Error> {
1070 if len == 0 {
1071 return Ok(Self::default());
1072 }
1073
1074 let raw = allocate_raw_region(len)?;
1075 let mut buffer = Self {
1076 base: Some(raw.base),
1077 data: raw.data,
1078 len,
1079 capacity: len,
1080 rounded_size: raw.rounded_size,
1081 total_size: raw.total_size,
1082 };
1083 buffer.as_mut_slice().fill(value);
1084 Ok(buffer)
1085 }
1086
1087 fn from_slice(src: &[u8]) -> Result<Self, std::io::Error> {
1088 let mut buffer = Self::new_filled(src.len(), 0)?;
1089 buffer.as_mut_slice().copy_from_slice(src);
1090 Ok(buffer)
1091 }
1092
1093 fn as_ptr(&self) -> *const u8 {
1094 self.data.as_ptr()
1095 }
1096
1097 fn as_mut_ptr(&mut self) -> *mut u8 {
1098 self.data.as_ptr()
1099 }
1100
1101 fn as_slice(&self) -> &[u8] {
1102 debug_assert!(self.len <= self.capacity);
1103 unsafe { std::slice::from_raw_parts(self.data.as_ptr(), self.len) }
1106 }
1107
1108 fn as_mut_slice(&mut self) -> &mut [u8] {
1109 debug_assert!(self.len <= self.capacity);
1110 unsafe { std::slice::from_raw_parts_mut(self.data.as_ptr(), self.len) }
1113 }
1114
1115 fn len(&self) -> usize {
1116 self.len
1117 }
1118
1119 fn is_empty(&self) -> bool {
1120 self.len == 0
1121 }
1122
1123 fn resize(&mut self, new_len: usize, value: u8) {
1124 if new_len == self.len {
1125 return;
1126 }
1127
1128 let mut resized = Self::new_filled(new_len, value).expect("protected resize failed");
1129 let len_to_copy = std::cmp::min(self.len, new_len);
1130 resized.as_mut_slice()[..len_to_copy].copy_from_slice(&self.as_slice()[..len_to_copy]);
1131 std::mem::swap(self, &mut resized);
1132 }
1133
1134 fn copy_from_slice(&mut self, other: &[u8]) {
1135 self.as_mut_slice().copy_from_slice(other);
1136 }
1137}
1138
1139impl Default for ProtectedBuffer {
1140 fn default() -> Self {
1141 Self {
1142 base: None,
1143 data: NonNull::dangling(),
1144 len: 0,
1145 capacity: 0,
1146 rounded_size: 0,
1147 total_size: 0,
1148 }
1149 }
1150}
1151
1152impl Clone for ProtectedBuffer {
1153 fn clone(&self) -> Self {
1154 Self::from_slice(self.as_slice()).expect("protected clone failed")
1155 }
1156}
1157
1158impl fmt::Debug for ProtectedBuffer {
1159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1160 f.debug_struct("ProtectedBuffer")
1161 .field("len", &self.len())
1162 .field("contents", &"[REDACTED]")
1163 .finish()
1164 }
1165}
1166
1167impl PartialEq for ProtectedBuffer {
1168 fn eq(&self, other: &Self) -> bool {
1169 self.as_slice().ct_eq(other.as_slice()).into()
1170 }
1171}
1172
1173impl Eq for ProtectedBuffer {}
1174
1175impl Zeroize for ProtectedBuffer {
1176 fn zeroize(&mut self) {
1177 self.as_mut_slice().zeroize();
1178 }
1179}
1180
1181impl Drop for ProtectedBuffer {
1182 fn drop(&mut self) {
1183 if let Some(base) = self.base.take() {
1184 if self.rounded_size != 0 {
1185 let _ = dryoc_mprotect_readwrite_ptr(self.data.as_ptr(), self.rounded_size);
1186 }
1187 self.as_mut_slice().zeroize();
1188 deallocate_raw_region(RawProtectedAllocation {
1189 base,
1190 data: self.data,
1191 rounded_size: self.rounded_size,
1192 total_size: self.total_size,
1193 });
1194 }
1195 }
1196}
1197
1198impl AsRef<[u8]> for ProtectedBuffer {
1199 fn as_ref(&self) -> &[u8] {
1200 self.as_slice()
1201 }
1202}
1203
1204impl AsMut<[u8]> for ProtectedBuffer {
1205 fn as_mut(&mut self) -> &mut [u8] {
1206 self.as_mut_slice()
1207 }
1208}
1209
1210impl std::ops::Deref for ProtectedBuffer {
1211 type Target = [u8];
1212
1213 fn deref(&self) -> &Self::Target {
1214 self.as_slice()
1215 }
1216}
1217
1218impl std::ops::DerefMut for ProtectedBuffer {
1219 fn deref_mut(&mut self) -> &mut Self::Target {
1220 self.as_mut_slice()
1221 }
1222}
1223
1224impl std::ops::Index<usize> for ProtectedBuffer {
1225 type Output = u8;
1226
1227 #[inline]
1228 fn index(&self, index: usize) -> &Self::Output {
1229 &self.as_slice()[index]
1230 }
1231}
1232
1233impl std::ops::IndexMut<usize> for ProtectedBuffer {
1234 #[inline]
1235 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1236 &mut self.as_mut_slice()[index]
1237 }
1238}
1239
1240macro_rules! impl_index_protected_buffer {
1241 ($range:ty) => {
1242 impl std::ops::Index<$range> for ProtectedBuffer {
1243 type Output = [u8];
1244
1245 #[inline]
1246 fn index(&self, index: $range) -> &Self::Output {
1247 &self.as_slice()[index]
1248 }
1249 }
1250 impl std::ops::IndexMut<$range> for ProtectedBuffer {
1251 #[inline]
1252 fn index_mut(&mut self, index: $range) -> &mut Self::Output {
1253 &mut self.as_mut_slice()[index]
1254 }
1255 }
1256 };
1257}
1258
1259impl_index_protected_buffer!(std::ops::Range<usize>);
1260impl_index_protected_buffer!(std::ops::RangeFull);
1261impl_index_protected_buffer!(std::ops::RangeFrom<usize>);
1262impl_index_protected_buffer!(std::ops::RangeInclusive<usize>);
1263impl_index_protected_buffer!(std::ops::RangeTo<usize>);
1264impl_index_protected_buffer!(std::ops::RangeToInclusive<usize>);
1265
1266#[cfg(feature = "nightly")]
1267unsafe impl Allocator for PageAlignedAllocator {
1272 #[inline]
1273 fn allocate(&self, layout: std::alloc::Layout) -> Result<NonNull<[u8]>, AllocError> {
1274 let pagesize = *PAGESIZE;
1275 if !pagesize.is_multiple_of(layout.align()) {
1276 return Err(AllocError);
1277 }
1278
1279 let raw = allocate_raw_region(layout.size()).map_err(|_| AllocError)?;
1280 unsafe {
1283 Ok(NonNull::new_unchecked(ptr::slice_from_raw_parts_mut(
1284 raw.data.as_ptr(),
1285 layout.size(),
1286 )))
1287 }
1288 }
1289
1290 #[inline]
1295 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: std::alloc::Layout) {
1298 let pagesize = *PAGESIZE;
1299
1300 let base_ptr = unsafe { ptr.as_ptr().sub(pagesize) };
1303 let Some(base) = NonNull::new(base_ptr) else {
1304 return;
1305 };
1306 let Ok(raw_layout) = checked_raw_region_layout(layout.size(), pagesize) else {
1307 return;
1308 };
1309 deallocate_raw_region(RawProtectedAllocation {
1310 base,
1311 data: ptr,
1312 rounded_size: raw_layout.rounded_size,
1313 total_size: raw_layout.total_size,
1314 });
1315 }
1316}
1317
1318#[derive(Zeroize, ZeroizeOnDrop, Debug, PartialEq, Eq, Clone)]
1323pub struct HeapByteArray<const LENGTH: usize>(ProtectedBuffer);
1324
1325#[derive(Zeroize, ZeroizeOnDrop, Debug, PartialEq, Eq, Clone, Default)]
1330pub struct HeapBytes(ProtectedBuffer);
1331
1332impl<A: Zeroize + NewBytes + Lockable<A>> NewLocked<A> for A {
1333 fn new_locked() -> Result<Protected<Self, traits::ReadWrite, traits::Locked>, error::Error> {
1334 Self::new_bytes().mlock()
1335 }
1336
1337 fn new_readonly_locked()
1338 -> Result<Protected<Self, traits::ReadOnly, traits::Locked>, error::Error> {
1339 Self::new_bytes()
1340 .mlock()
1341 .and_then(|p| p.mprotect_readonly())
1342 }
1343
1344 fn generate_locked() -> Result<Protected<Self, traits::ReadWrite, traits::Locked>, error::Error>
1345 {
1346 let mut res = Self::new_bytes().mlock()?;
1347 copy_randombytes(res.as_mut_slice());
1348 Ok(res)
1349 }
1350
1351 fn generate_readonly_locked()
1352 -> Result<Protected<Self, traits::ReadOnly, traits::Locked>, error::Error> {
1353 Self::generate_locked().and_then(|s| s.mprotect_readonly())
1354 }
1355}
1356
1357impl<A: Zeroize + NewBytes + ResizableBytes + Lockable<A>> NewLockedFromSlice<A> for A {
1358 fn from_slice_into_locked(
1360 src: &[u8],
1361 ) -> Result<Protected<Self, traits::ReadWrite, traits::Locked>, crate::error::Error> {
1362 let mut res = Self::new_bytes().mlock()?;
1363 res.resize(src.len(), 0);
1364 res.as_mut_slice().copy_from_slice(src);
1365 Ok(res)
1366 }
1367
1368 fn from_slice_into_readonly_locked(
1370 src: &[u8],
1371 ) -> Result<Protected<Self, traits::ReadOnly, traits::Locked>, crate::error::Error> {
1372 Self::from_slice_into_locked(src).and_then(|s| s.mprotect_readonly())
1373 }
1374}
1375
1376impl<const LENGTH: usize> NewLockedFromSlice<HeapByteArray<LENGTH>> for HeapByteArray<LENGTH> {
1377 fn from_slice_into_locked(
1379 other: &[u8],
1380 ) -> Result<Protected<Self, traits::ReadWrite, traits::Locked>, crate::error::Error> {
1381 if other.len() != LENGTH {
1382 return Err(length_error!(crate::ErrorContext::Slice, other.len(), exact LENGTH));
1383 }
1384 let mut res = Self::new_bytes().mlock()?;
1385 res.as_mut_slice().copy_from_slice(other);
1386 Ok(res)
1387 }
1388
1389 fn from_slice_into_readonly_locked(
1390 other: &[u8],
1391 ) -> Result<Protected<Self, traits::ReadOnly, traits::Locked>, crate::error::Error> {
1392 Self::from_slice_into_locked(other).and_then(|s| s.mprotect_readonly())
1393 }
1394}
1395
1396impl<const LENGTH: usize> Bytes for HeapByteArray<LENGTH> {
1397 #[inline]
1398 fn as_slice(&self) -> &[u8] {
1399 &self.0
1400 }
1401
1402 #[inline]
1403 fn len(&self) -> usize {
1404 self.0.len()
1405 }
1406
1407 #[inline]
1408 fn is_empty(&self) -> bool {
1409 self.0.is_empty()
1410 }
1411}
1412
1413impl Bytes for HeapBytes {
1414 #[inline]
1415 fn as_slice(&self) -> &[u8] {
1416 &self.0
1417 }
1418
1419 #[inline]
1420 fn len(&self) -> usize {
1421 self.0.len()
1422 }
1423
1424 #[inline]
1425 fn is_empty(&self) -> bool {
1426 self.0.is_empty()
1427 }
1428}
1429
1430impl<const LENGTH: usize> MutBytes for HeapByteArray<LENGTH> {
1431 #[inline]
1432 fn as_mut_slice(&mut self) -> &mut [u8] {
1433 self.0.as_mut_slice()
1434 }
1435
1436 fn copy_from_slice(&mut self, other: &[u8]) {
1437 self.0.copy_from_slice(other)
1438 }
1439}
1440
1441impl NewBytes for HeapBytes {
1442 fn new_bytes() -> Self {
1443 Self::default()
1444 }
1445}
1446
1447impl MutBytes for HeapBytes {
1448 #[inline]
1449 fn as_mut_slice(&mut self) -> &mut [u8] {
1450 self.0.as_mut_slice()
1451 }
1452
1453 fn copy_from_slice(&mut self, other: &[u8]) {
1454 self.0.copy_from_slice(other)
1455 }
1456}
1457
1458impl ResizableBytes for HeapBytes {
1459 fn resize(&mut self, new_len: usize, value: u8) {
1460 self.0.resize(new_len, value);
1461 }
1462}
1463
1464impl<A: Zeroize + NewBytes + ResizableBytes + Lockable<A>> ResizableBytes
1465 for Protected<A, traits::ReadWrite, traits::Locked>
1466{
1467 fn resize(&mut self, new_len: usize, value: u8) {
1468 match &mut self.i {
1469 Some(d) => {
1470 let mut new = A::new_bytes();
1472 new.resize(new_len, value);
1474 let mut locked = new.mlock().expect("unable to lock on resize");
1476 let len_to_copy = std::cmp::min(new_len, d.a.as_slice().len());
1477 locked.i.as_mut().unwrap().a.as_mut_slice()[..len_to_copy]
1478 .copy_from_slice(&d.a.as_slice()[..len_to_copy]);
1479 std::mem::swap(&mut locked.i, &mut self.i);
1480 }
1483 None => panic!("invalid array"),
1484 }
1485 }
1486}
1487
1488impl<A: Zeroize + NewBytes + ResizableBytes + Lockable<A>> ResizableBytes
1489 for Protected<A, traits::ReadWrite, traits::Unlocked>
1490{
1491 fn resize(&mut self, new_len: usize, value: u8) {
1492 match &mut self.i {
1493 Some(d) => d.a.resize(new_len, value),
1494 None => panic!("invalid array"),
1495 }
1496 }
1497}
1498
1499impl<A: Zeroize + MutBytes, LM: traits::LockMode> MutBytes for Protected<A, traits::ReadWrite, LM> {
1500 #[inline]
1501 fn as_mut_slice(&mut self) -> &mut [u8] {
1502 match &mut self.i {
1503 Some(d) => d.a.as_mut_slice(),
1504 None => panic!("invalid array"),
1505 }
1506 }
1507
1508 fn copy_from_slice(&mut self, other: &[u8]) {
1509 match &mut self.i {
1510 Some(d) => d.a.copy_from_slice(other),
1511 None => panic!("invalid array"),
1512 }
1513 }
1514}
1515
1516impl<const LENGTH: usize> std::convert::AsRef<[u8; LENGTH]> for HeapByteArray<LENGTH> {
1517 fn as_ref(&self) -> &[u8; LENGTH] {
1518 let arr = self.0.as_ptr() as *const [u8; LENGTH];
1519 unsafe { &*arr }
1522 }
1523}
1524
1525impl<const LENGTH: usize> std::convert::AsMut<[u8; LENGTH]> for HeapByteArray<LENGTH> {
1526 fn as_mut(&mut self) -> &mut [u8; LENGTH] {
1527 let arr = self.0.as_mut_ptr() as *mut [u8; LENGTH];
1528 unsafe { &mut *arr }
1531 }
1532}
1533
1534impl<const LENGTH: usize> std::convert::AsRef<[u8]> for HeapByteArray<LENGTH> {
1535 fn as_ref(&self) -> &[u8] {
1536 self.0.as_ref()
1537 }
1538}
1539
1540impl std::convert::AsRef<[u8]> for HeapBytes {
1541 fn as_ref(&self) -> &[u8] {
1542 self.0.as_ref()
1543 }
1544}
1545
1546impl<const LENGTH: usize> std::convert::AsMut<[u8]> for HeapByteArray<LENGTH> {
1547 fn as_mut(&mut self) -> &mut [u8] {
1548 self.0.as_mut()
1549 }
1550}
1551
1552impl std::convert::AsMut<[u8]> for HeapBytes {
1553 fn as_mut(&mut self) -> &mut [u8] {
1554 self.0.as_mut()
1555 }
1556}
1557
1558impl<const LENGTH: usize> std::ops::Deref for HeapByteArray<LENGTH> {
1559 type Target = [u8];
1560
1561 fn deref(&self) -> &Self::Target {
1562 &self.0
1563 }
1564}
1565
1566impl<const LENGTH: usize> std::ops::DerefMut for HeapByteArray<LENGTH> {
1567 fn deref_mut(&mut self) -> &mut Self::Target {
1568 &mut self.0
1569 }
1570}
1571
1572impl std::ops::Deref for HeapBytes {
1573 type Target = [u8];
1574
1575 fn deref(&self) -> &Self::Target {
1576 &self.0
1577 }
1578}
1579
1580impl std::ops::DerefMut for HeapBytes {
1581 fn deref_mut(&mut self) -> &mut Self::Target {
1582 &mut self.0
1583 }
1584}
1585
1586impl<A: Bytes + Zeroize, LM: traits::LockMode> std::ops::Deref
1587 for Protected<A, traits::ReadOnly, LM>
1588{
1589 type Target = [u8];
1590
1591 fn deref(&self) -> &Self::Target {
1592 self.i.as_ref().unwrap().a.as_slice()
1593 }
1594}
1595
1596impl<A: Bytes + Zeroize, LM: traits::LockMode> std::ops::Deref
1597 for Protected<A, traits::ReadWrite, LM>
1598{
1599 type Target = [u8];
1600
1601 fn deref(&self) -> &Self::Target {
1602 self.i.as_ref().unwrap().a.as_slice()
1603 }
1604}
1605
1606impl<A: MutBytes + Zeroize, LM: traits::LockMode> std::ops::DerefMut
1607 for Protected<A, traits::ReadWrite, LM>
1608{
1609 fn deref_mut(&mut self) -> &mut Self::Target {
1610 self.i.as_mut().unwrap().a.as_mut_slice()
1611 }
1612}
1613
1614impl<const LENGTH: usize> std::ops::Index<usize> for HeapByteArray<LENGTH> {
1615 type Output = u8;
1616
1617 #[inline]
1618 fn index(&self, index: usize) -> &Self::Output {
1619 &self.0[index]
1620 }
1621}
1622impl<const LENGTH: usize> std::ops::IndexMut<usize> for HeapByteArray<LENGTH> {
1623 #[inline]
1624 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1625 &mut self.0[index]
1626 }
1627}
1628
1629macro_rules! impl_index_heapbytearray {
1630 ($range:ty) => {
1631 impl<const LENGTH: usize> std::ops::Index<$range> for HeapByteArray<LENGTH> {
1632 type Output = [u8];
1633
1634 #[inline]
1635 fn index(&self, index: $range) -> &Self::Output {
1636 &self.0[index]
1637 }
1638 }
1639 impl<const LENGTH: usize> std::ops::IndexMut<$range> for HeapByteArray<LENGTH> {
1640 #[inline]
1641 fn index_mut(&mut self, index: $range) -> &mut Self::Output {
1642 &mut self.0[index]
1643 }
1644 }
1645 };
1646}
1647
1648impl_index_heapbytearray!(std::ops::Range<usize>);
1649impl_index_heapbytearray!(std::ops::RangeFull);
1650impl_index_heapbytearray!(std::ops::RangeFrom<usize>);
1651impl_index_heapbytearray!(std::ops::RangeInclusive<usize>);
1652impl_index_heapbytearray!(std::ops::RangeTo<usize>);
1653impl_index_heapbytearray!(std::ops::RangeToInclusive<usize>);
1654
1655impl<const LENGTH: usize> Default for HeapByteArray<LENGTH> {
1656 fn default() -> Self {
1657 Self(ProtectedBuffer::new_filled(LENGTH, 0).expect("protected allocation failed"))
1658 }
1659}
1660
1661impl<A: Zeroize + NewBytes + Lockable<A> + NewLocked<A>> Default
1662 for Protected<A, traits::ReadWrite, traits::Locked>
1663{
1664 fn default() -> Self {
1665 A::new_locked().expect("mlock failed")
1666 }
1667}
1668
1669impl std::ops::Index<usize> for HeapBytes {
1670 type Output = u8;
1671
1672 #[inline]
1673 fn index(&self, index: usize) -> &Self::Output {
1674 &self.0[index]
1675 }
1676}
1677impl std::ops::IndexMut<usize> for HeapBytes {
1678 #[inline]
1679 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1680 &mut self.0[index]
1681 }
1682}
1683
1684macro_rules! impl_index_heapbytes {
1685 ($range:ty) => {
1686 impl std::ops::Index<$range> for HeapBytes {
1687 type Output = [u8];
1688
1689 #[inline]
1690 fn index(&self, index: $range) -> &Self::Output {
1691 &self.0[index]
1692 }
1693 }
1694 impl std::ops::IndexMut<$range> for HeapBytes {
1695 #[inline]
1696 fn index_mut(&mut self, index: $range) -> &mut Self::Output {
1697 &mut self.0[index]
1698 }
1699 }
1700 };
1701}
1702
1703impl_index_heapbytes!(std::ops::Range<usize>);
1704impl_index_heapbytes!(std::ops::RangeFull);
1705impl_index_heapbytes!(std::ops::RangeFrom<usize>);
1706impl_index_heapbytes!(std::ops::RangeInclusive<usize>);
1707impl_index_heapbytes!(std::ops::RangeTo<usize>);
1708impl_index_heapbytes!(std::ops::RangeToInclusive<usize>);
1709
1710impl<const LENGTH: usize> From<&[u8; LENGTH]> for HeapByteArray<LENGTH> {
1711 fn from(src: &[u8; LENGTH]) -> Self {
1712 let mut arr = Self::default();
1713 arr.0.copy_from_slice(src);
1714 arr
1715 }
1716}
1717
1718impl<const LENGTH: usize> From<[u8; LENGTH]> for HeapByteArray<LENGTH> {
1719 fn from(mut src: [u8; LENGTH]) -> Self {
1720 let ret = Self::from(&src);
1721 src.zeroize();
1723 ret
1724 }
1725}
1726
1727impl<const LENGTH: usize> TryFrom<&[u8]> for HeapByteArray<LENGTH> {
1728 type Error = error::Error;
1729
1730 fn try_from(src: &[u8]) -> Result<Self, Self::Error> {
1731 if src.len() != LENGTH {
1732 Err(length_error!(crate::ErrorContext::Slice, src.len(), exact LENGTH))
1733 } else {
1734 let mut arr = Self::default();
1735 arr.0.copy_from_slice(src);
1736 Ok(arr)
1737 }
1738 }
1739}
1740
1741impl From<&[u8]> for HeapBytes {
1742 fn from(src: &[u8]) -> Self {
1743 Self(ProtectedBuffer::from_slice(src).expect("protected allocation failed"))
1744 }
1745}
1746
1747impl<const LENGTH: usize> ByteArray<LENGTH> for HeapByteArray<LENGTH> {
1748 #[inline]
1749 fn as_array(&self) -> &[u8; LENGTH] {
1750 let ptr = self.0.as_ptr() as *const [u8; LENGTH];
1751 unsafe { &*ptr }
1754 }
1755}
1756
1757impl<const LENGTH: usize> NewBytes for HeapByteArray<LENGTH> {
1758 fn new_bytes() -> Self {
1759 Self::default()
1760 }
1761}
1762
1763impl NewBytes for Protected<HeapBytes, traits::ReadWrite, traits::Locked> {
1764 fn new_bytes() -> Self {
1765 match HeapBytes::new_locked() {
1766 Ok(r) => r,
1767 Err(err) => panic!("Error creating locked bytes: {:?}", err),
1768 }
1769 }
1770}
1771
1772impl<const LENGTH: usize> NewBytes
1773 for Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Locked>
1774{
1775 fn new_bytes() -> Self {
1776 match HeapByteArray::<LENGTH>::new_locked() {
1777 Ok(r) => r,
1778 Err(err) => panic!("Error creating locked bytes: {:?}", err),
1779 }
1780 }
1781}
1782
1783impl<const LENGTH: usize> NewByteArray<LENGTH>
1784 for Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Locked>
1785{
1786 fn new_byte_array() -> Self {
1787 match HeapByteArray::<LENGTH>::new_locked() {
1788 Ok(r) => r,
1789 Err(err) => panic!("Error creating locked bytes: {:?}", err),
1790 }
1791 }
1792
1793 fn r#gen() -> Self {
1794 match HeapByteArray::<LENGTH>::new_locked() {
1795 Ok(mut r) => {
1796 copy_randombytes(r.as_mut_slice());
1797 r
1798 }
1799 Err(err) => panic!("Error creating locked bytes: {:?}", err),
1800 }
1801 }
1802}
1803
1804impl<const LENGTH: usize> NewByteArray<LENGTH> for HeapByteArray<LENGTH> {
1805 fn new_byte_array() -> Self {
1806 Self::default()
1807 }
1808
1809 fn r#gen() -> Self {
1811 let mut res = Self::default();
1812 copy_randombytes(res.as_mut_slice());
1813 res
1814 }
1815}
1816
1817impl<const LENGTH: usize> MutByteArray<LENGTH> for HeapByteArray<LENGTH> {
1818 fn as_mut_array(&mut self) -> &mut [u8; LENGTH] {
1819 let ptr = self.0.as_mut_ptr() as *mut [u8; LENGTH];
1820 unsafe { &mut *ptr }
1823 }
1824}
1825
1826impl<const LENGTH: usize> ByteArray<LENGTH>
1827 for Protected<HeapByteArray<LENGTH>, traits::ReadOnly, traits::Unlocked>
1828{
1829 #[inline]
1830 fn as_array(&self) -> &[u8; LENGTH] {
1831 match &self.i {
1832 Some(d) => d.a.as_array(),
1833 None => panic!("invalid array"),
1834 }
1835 }
1836}
1837
1838impl<const LENGTH: usize> ByteArray<LENGTH>
1839 for Protected<HeapByteArray<LENGTH>, traits::ReadOnly, traits::Locked>
1840{
1841 #[inline]
1842 fn as_array(&self) -> &[u8; LENGTH] {
1843 match &self.i {
1844 Some(d) => d.a.as_array(),
1845 None => panic!("invalid array"),
1846 }
1847 }
1848}
1849
1850impl<const LENGTH: usize> ByteArray<LENGTH>
1851 for Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Unlocked>
1852{
1853 #[inline]
1854 fn as_array(&self) -> &[u8; LENGTH] {
1855 match &self.i {
1856 Some(d) => d.a.as_array(),
1857 None => panic!("invalid array"),
1858 }
1859 }
1860}
1861
1862impl<const LENGTH: usize> ByteArray<LENGTH>
1863 for Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Locked>
1864{
1865 #[inline]
1866 fn as_array(&self) -> &[u8; LENGTH] {
1867 match &self.i {
1868 Some(d) => d.a.as_array(),
1869 None => panic!("invalid array"),
1870 }
1871 }
1872}
1873
1874impl<const LENGTH: usize> MutByteArray<LENGTH>
1875 for Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Locked>
1876{
1877 #[inline]
1878 fn as_mut_array(&mut self) -> &mut [u8; LENGTH] {
1879 match &mut self.i {
1880 Some(d) => d.a.as_mut_array(),
1881 None => panic!("invalid array"),
1882 }
1883 }
1884}
1885
1886impl<const LENGTH: usize> MutByteArray<LENGTH>
1887 for Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Unlocked>
1888{
1889 #[inline]
1890 fn as_mut_array(&mut self) -> &mut [u8; LENGTH] {
1891 match &mut self.i {
1892 Some(d) => d.a.as_mut_array(),
1893 None => panic!("invalid array"),
1894 }
1895 }
1896}
1897
1898impl<const LENGTH: usize> AsMut<[u8; LENGTH]>
1899 for Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Locked>
1900{
1901 fn as_mut(&mut self) -> &mut [u8; LENGTH] {
1902 match &mut self.i {
1903 Some(d) => d.a.as_mut(),
1904 None => panic!("invalid array"),
1905 }
1906 }
1907}
1908
1909impl<const LENGTH: usize> AsMut<[u8; LENGTH]>
1910 for Protected<HeapByteArray<LENGTH>, traits::ReadWrite, traits::Unlocked>
1911{
1912 fn as_mut(&mut self) -> &mut [u8; LENGTH] {
1913 match &mut self.i {
1914 Some(d) => d.a.as_mut(),
1915 None => panic!("invalid array"),
1916 }
1917 }
1918}
1919
1920impl<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> Drop
1921 for Protected<A, PM, LM>
1922{
1923 fn drop(&mut self) {
1924 let Some(mut data) = self.i.take() else {
1925 return;
1926 };
1927
1928 let writable = data.a.as_slice().is_empty()
1929 || data.pm == int::ProtectMode::ReadWrite
1930 || match dryoc_mprotect_readwrite(data.a.as_slice()) {
1931 Ok(()) => true,
1932 Err(err) => abort_protected_memory_failure("making memory writable for drop", err),
1933 };
1934
1935 if writable {
1936 data.a.zeroize();
1937 }
1938
1939 if data.lm == int::LockMode::Locked {
1940 match dryoc_munlock(data.a.as_slice()) {
1941 Ok(()) => data.lm = int::LockMode::Unlocked,
1942 Err(err) => abort_protected_memory_failure("unlocking memory for drop", err),
1943 }
1944 }
1945 }
1946}
1947
1948impl<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> ZeroizeOnDrop
1949 for Protected<A, PM, LM>
1950{
1951}
1952
1953impl<A: Zeroize + Bytes, PM: traits::ProtectMode, LM: traits::LockMode> Zeroize
1954 for Protected<A, PM, LM>
1955{
1956 fn zeroize(&mut self) {
1957 let Some(data) = &mut self.i else {
1958 return;
1959 };
1960 if data.a.as_slice().is_empty() {
1961 return;
1962 }
1963
1964 let previous_mode = data.pm.clone();
1965 if previous_mode != int::ProtectMode::ReadWrite
1966 && let Err(error) = dryoc_mprotect_readwrite(data.a.as_slice())
1967 {
1968 abort_protected_memory_failure("making memory writable for zeroization", error);
1969 }
1970
1971 data.a.zeroize();
1972
1973 if previous_mode != int::ProtectMode::ReadWrite
1974 && let Err(error) = dryoc_mprotect_mode(data.a.as_slice(), &previous_mode)
1975 {
1976 abort_protected_memory_failure("restoring memory protection after zeroization", error);
1977 }
1978 }
1979}
1980
1981fn abort_protected_memory_failure(_operation: &str, _error: std::io::Error) -> ! {
1982 std::process::abort()
1983}
1984
1985#[cfg(test)]
1986mod tests {
1987 use proptest::prelude::*;
1988
1989 use super::*;
1990
1991 #[test]
1992 fn protected_byte_array_debug_redacts_contents() {
1993 let bytes = HeapByteArray::from(StackByteArray::from([0xabu8; 4]));
1994 let debug = format!("{bytes:?}");
1995
1996 assert!(debug.contains("[REDACTED]"));
1997 assert!(!debug.contains("171"));
1998 }
1999
2000 fn interesting_lengths() -> impl Strategy<Value = usize> {
2001 let pagesize = *PAGESIZE;
2002 let max = pagesize.saturating_mul(2).saturating_add(8);
2003
2004 prop_oneof![
2005 Just(0usize),
2006 Just(1),
2007 0usize..=128,
2008 pagesize.saturating_sub(8)..=pagesize.saturating_add(8),
2009 pagesize.saturating_mul(2).saturating_sub(8)..=max,
2010 ]
2011 .boxed()
2012 }
2013
2014 fn small_lengths() -> impl Strategy<Value = usize> {
2015 prop_oneof![Just(0usize), Just(1), 0usize..=256].boxed()
2016 }
2017
2018 fn interesting_bytes() -> impl Strategy<Value = Vec<u8>> {
2019 interesting_lengths()
2020 .prop_flat_map(|len| prop::collection::vec(any::<u8>(), len))
2021 .boxed()
2022 }
2023
2024 fn small_bytes() -> impl Strategy<Value = Vec<u8>> {
2025 small_lengths()
2026 .prop_flat_map(|len| prop::collection::vec(any::<u8>(), len))
2027 .boxed()
2028 }
2029
2030 #[cfg_attr(
2031 tarpaulin,
2032 ignore = "tarpaulin can segfault while tracing mlock/mprotect tests"
2033 )]
2034 #[test]
2035 fn test_lock_unlock() {
2036 use crate::dryocstream::Key;
2037
2038 let key = Key::generate();
2039 let key_clone = key.clone();
2040
2041 let locked_key = key.mlock().expect("lock failed");
2042
2043 let unlocked_key = locked_key.munlock().expect("unlock failed");
2044
2045 assert_eq!(unlocked_key.as_slice(), key_clone.as_slice());
2046 }
2047
2048 #[cfg_attr(
2049 tarpaulin,
2050 ignore = "tarpaulin can segfault while tracing mlock/mprotect tests"
2051 )]
2052 #[test]
2053 fn explicit_zeroize_preserves_locked_readwrite_state() {
2054 let mut locked =
2055 HeapBytes::from_slice_into_locked(b"sensitive").expect("locked allocation failed");
2056
2057 locked.zeroize();
2058
2059 assert_eq!(locked.as_slice(), &[0; 9]);
2060 let state = locked.i.as_ref().expect("protected state missing");
2061 assert_eq!(state.lm, int::LockMode::Locked);
2062 assert_eq!(state.pm, int::ProtectMode::ReadWrite);
2063
2064 let unlocked = locked.munlock().expect("unlock after zeroize failed");
2065 assert_eq!(unlocked.as_slice(), &[0; 9]);
2066 }
2067
2068 #[cfg(unix)]
2069 #[cfg_attr(
2070 tarpaulin,
2071 ignore = "tarpaulin can segfault while tracing mlock/mprotect tests"
2072 )]
2073 #[test]
2074 fn explicit_zeroize_restores_readonly_protection() {
2075 let mut readonly = HeapBytes::from_slice_into_readonly_locked(b"sensitive")
2076 .expect("read-only locked allocation failed");
2077
2078 readonly.zeroize();
2079
2080 assert_eq!(readonly.as_slice(), &[0; 9]);
2081 let state = readonly.i.as_ref().expect("protected state missing");
2082 assert_eq!(state.lm, int::LockMode::Locked);
2083 assert_eq!(state.pm, int::ProtectMode::ReadOnly);
2084
2085 let child = unsafe { libc::fork() };
2087 assert!(child >= 0, "fork failed");
2088 if child == 0 {
2089 let data = readonly.as_slice().as_ptr() as *mut u8;
2090 unsafe {
2093 std::ptr::write_volatile(data, 1);
2094 libc::_exit(0);
2095 }
2096 }
2097
2098 let mut status = 0;
2099 let wait_ret = unsafe { libc::waitpid(child, &mut status, 0) };
2102 assert_eq!(wait_ret, child);
2103 assert!(
2104 libc::WIFSIGNALED(status),
2105 "child unexpectedly wrote to explicitly zeroized read-only memory"
2106 );
2107
2108 let readwrite = readonly
2109 .mprotect_readwrite()
2110 .expect("read-write transition failed");
2111 let unlocked = readwrite.munlock().expect("unlock failed");
2112 assert_eq!(unlocked.as_slice(), &[0; 9]);
2113 }
2114
2115 #[cfg_attr(
2116 tarpaulin,
2117 ignore = "tarpaulin can segfault while tracing mlock/mprotect tests"
2118 )]
2119 #[test]
2120 fn test_protect_unprotect() {
2121 use crate::dryocstream::Key;
2122
2123 let key = Key::generate();
2124 let key_clone = key.clone();
2125
2126 let readonly_key = key.mprotect_readonly().expect("mprotect failed");
2127 assert_eq!(readonly_key.as_slice(), key_clone.as_slice());
2128
2129 let mut readwrite_key = readonly_key.mprotect_readwrite().expect("mprotect failed");
2130 assert_eq!(readwrite_key.as_slice(), key_clone.as_slice());
2131
2132 readwrite_key.as_mut_slice()[0] = 0;
2134 }
2135
2136 #[cfg(feature = "nightly")]
2137 #[test]
2138 fn test_allocator() {
2139 let mut vec: Vec<i32, _> = Vec::new_in(PageAlignedAllocator);
2140
2141 vec.push(1);
2142 vec.push(2);
2143 vec.push(3);
2144
2145 for i in 0..5000 {
2146 vec.push(i);
2147 }
2148
2149 vec.resize(5, 0);
2150
2151 assert_eq!([1, 2, 3, 0, 1], vec.as_slice());
2152 }
2153
2154 #[cfg(feature = "nightly")]
2155 #[test]
2156 fn test_allocator_honors_supported_alignment() {
2157 let allocator = PageAlignedAllocator;
2158 let layout = std::alloc::Layout::from_size_align(1, *PAGESIZE).unwrap();
2159 let allocation = allocator.allocate(layout).unwrap();
2160 let data = allocation.as_ptr() as *mut u8;
2161
2162 assert_eq!(data.addr() % layout.align(), 0);
2163
2164 unsafe { allocator.deallocate(NonNull::new_unchecked(data), layout) };
2166 }
2167
2168 #[cfg(feature = "nightly")]
2169 #[test]
2170 fn test_allocator_rejects_unsupported_alignment() {
2171 let unsupported_alignment = PAGESIZE.checked_mul(2).unwrap();
2172 let layout = std::alloc::Layout::from_size_align(1, unsupported_alignment).unwrap();
2173
2174 assert!(PageAlignedAllocator.allocate(layout).is_err());
2175 }
2176
2177 #[cfg(feature = "nightly")]
2178 #[test]
2179 fn test_allocator_handles_zero_sized_layout() {
2180 let allocator = PageAlignedAllocator;
2181 let layout = std::alloc::Layout::from_size_align(0, 1).unwrap();
2182 let allocation = allocator.allocate(layout).unwrap();
2183 let data = allocation.as_ptr() as *mut u8;
2184
2185 assert_eq!(allocation.len(), 0);
2186 assert_eq!(data.addr() % layout.align(), 0);
2187
2188 unsafe { allocator.deallocate(NonNull::new_unchecked(data), layout) };
2190 }
2191
2192 #[test]
2193 fn test_page_rounding() {
2194 let pagesize = *PAGESIZE;
2195
2196 assert_eq!(_page_round(0, pagesize), Some(0));
2197 assert_eq!(_page_round(1, pagesize), Some(pagesize));
2198 assert_eq!(_page_round(pagesize, pagesize), Some(pagesize));
2199 assert_eq!(_page_round(pagesize + 1, pagesize), Some(pagesize * 2));
2200 assert_eq!(_page_round(usize::MAX, pagesize), None);
2201 }
2202
2203 #[cfg(unix)]
2204 #[test]
2205 fn test_page_size_from_sysconf_handles_error_sentinel() {
2206 assert_eq!(page_size_from_sysconf(-1), DEFAULT_PAGESIZE);
2207 assert_eq!(page_size_from_sysconf(0), DEFAULT_PAGESIZE);
2208 assert_eq!(page_size_from_sysconf(8192), 8192);
2209 }
2210
2211 #[test]
2212 fn test_empty_heapbytes_and_locking() {
2213 let empty = HeapBytes::default();
2214 assert!(empty.is_empty());
2215 assert_eq!(empty.as_slice().len(), 0);
2216
2217 let locked: LockedBytes = HeapBytes::new_locked().expect("empty mlock failed");
2218 assert!(locked.is_empty());
2219
2220 let unlocked = locked.munlock().expect("empty munlock failed");
2221 assert!(unlocked.is_empty());
2222 }
2223
2224 #[test]
2225 fn test_heapbytes_resize_grow_shrink_and_fill() {
2226 let mut bytes = HeapBytes::default();
2227 bytes.resize(3, 0x7a);
2228 assert_eq!(bytes.as_slice(), &[0x7a, 0x7a, 0x7a]);
2229
2230 bytes.as_mut_slice()[1] = 0x11;
2231 bytes.resize(5, 0x5a);
2232 assert_eq!(bytes.as_slice(), &[0x7a, 0x11, 0x7a, 0x5a, 0x5a]);
2233
2234 bytes.resize(2, 0);
2235 assert_eq!(bytes.as_slice(), &[0x7a, 0x11]);
2236
2237 bytes.resize(0, 0);
2238 assert!(bytes.is_empty());
2239 }
2240
2241 proptest! {
2242 #![proptest_config(ProptestConfig::with_cases(64))]
2243
2244 #[test]
2245 fn proptest_heapbytes_roundtrip_clone_and_mutation(data in interesting_bytes()) {
2246 let bytes = HeapBytes::from(data.as_slice());
2247 prop_assert_eq!(bytes.len(), data.len());
2248 prop_assert_eq!(bytes.as_slice(), data.as_slice());
2249 prop_assert_eq!(bytes.as_ref(), data.as_slice());
2250
2251 let mut cloned = bytes.clone();
2252 prop_assert_eq!(&cloned, &bytes);
2253 prop_assert_eq!(cloned.as_slice(), data.as_slice());
2254
2255 if !data.is_empty() {
2256 prop_assert_eq!(cloned[0], data[0]);
2257
2258 let last = data.len() - 1;
2259 prop_assert_eq!(cloned[last], data[last]);
2260
2261 cloned[0] = cloned[0].wrapping_add(1);
2262 prop_assert_ne!(cloned[0], data[0]);
2263 prop_assert_eq!(&cloned[1..], &data[1..]);
2264 }
2265 }
2266
2267 #[test]
2268 fn proptest_heapbytes_resize_matches_vec_model(
2269 initial in interesting_bytes(),
2270 ops in prop::collection::vec((interesting_lengths(), any::<u8>()), 0..12),
2271 ) {
2272 let mut bytes = HeapBytes::from(initial.as_slice());
2273 let mut model = initial;
2274
2275 for (new_len, value) in ops {
2276 bytes.resize(new_len, value);
2277 model.resize(new_len, value);
2278 prop_assert_eq!(bytes.as_slice(), model.as_slice());
2279 }
2280 }
2281
2282 #[test]
2283 fn proptest_protection_transitions_preserve_bytes(data in interesting_bytes()) {
2284 let protected =
2285 Protected::<HeapBytes, traits::ReadWrite, traits::Unlocked>::new_with(
2286 HeapBytes::from(data.as_slice()),
2287 );
2288
2289 let readonly = protected
2290 .mprotect_readonly()
2291 .expect("readonly mprotect failed");
2292 prop_assert_eq!(readonly.as_slice(), data.as_slice());
2293
2294 let readwrite = readonly
2295 .mprotect_readwrite()
2296 .expect("readwrite mprotect failed");
2297 prop_assert_eq!(readwrite.as_slice(), data.as_slice());
2298
2299 let noaccess = readwrite
2300 .mprotect_noaccess()
2301 .expect("noaccess mprotect failed");
2302 let readwrite = noaccess
2303 .mprotect_readwrite()
2304 .expect("readwrite mprotect failed");
2305 prop_assert_eq!(readwrite.as_slice(), data.as_slice());
2306 }
2307 }
2308
2309 proptest! {
2310 #![proptest_config(ProptestConfig::with_cases(32))]
2311
2312 #[test]
2313 fn proptest_locked_heapbytes_resize_matches_vec_model(
2314 initial in small_bytes(),
2315 ops in prop::collection::vec((small_lengths(), any::<u8>()), 0..8),
2316 ) {
2317 let mut locked = HeapBytes::from_slice_into_locked(initial.as_slice())
2318 .expect("locked allocation failed");
2319 let mut model = initial;
2320
2321 for (new_len, value) in ops {
2322 locked.resize(new_len, value);
2323 model.resize(new_len, value);
2324 prop_assert_eq!(locked.as_slice(), model.as_slice());
2325 }
2326
2327 let unlocked = locked.munlock().expect("munlock failed");
2328 prop_assert_eq!(unlocked.as_slice(), model.as_slice());
2329 }
2330
2331 #[test]
2332 fn proptest_heapbytearray_exact_size_views(data in any::<[u8; 32]>()) {
2333 let mut bytes = HeapByteArray::<32>::from(&data);
2334
2335 prop_assert_eq!(bytes.as_array(), &data);
2336 prop_assert_eq!(AsRef::<[u8; 32]>::as_ref(&bytes), &data);
2337 prop_assert_eq!(bytes.as_slice(), &data);
2338
2339 let mut expected = data;
2340 bytes.as_mut_array()[7] ^= 0xa5;
2341 expected[7] ^= 0xa5;
2342 prop_assert_eq!(bytes.as_array(), &expected);
2343
2344 AsMut::<[u8; 32]>::as_mut(&mut bytes)[24] = 0x5a;
2345 expected[24] = 0x5a;
2346 prop_assert_eq!(bytes.as_slice(), &expected);
2347 }
2348 }
2349
2350 #[test]
2351 fn test_heapbytearray_exact_size_views() {
2352 let mut bytes = HeapByteArray::<4>::default();
2353 bytes.as_mut_array().copy_from_slice(&[1, 2, 3, 4]);
2354
2355 assert_eq!(bytes.as_array(), &[1, 2, 3, 4]);
2356 assert_eq!(AsRef::<[u8; 4]>::as_ref(&bytes), &[1, 2, 3, 4]);
2357
2358 AsMut::<[u8; 4]>::as_mut(&mut bytes)[2] = 9;
2359 assert_eq!(bytes.as_slice(), &[1, 2, 9, 4]);
2360 }
2361
2362 #[cfg_attr(
2363 tarpaulin,
2364 ignore = "tarpaulin can segfault while tracing mlock/mprotect tests"
2365 )]
2366 #[test]
2367 fn test_mprotect_handles_single_byte_slice() {
2368 let mut vec = HeapBytes::from(&[1u8][..]);
2369
2370 dryoc_mprotect_readonly(vec.as_slice()).expect("readonly mprotect failed");
2371 dryoc_mprotect_readwrite(vec.as_slice()).expect("readwrite mprotect failed");
2372 vec[0] = 2;
2373
2374 assert_eq!(vec[0], 2);
2375 }
2376
2377 #[cfg_attr(
2378 tarpaulin,
2379 ignore = "tarpaulin can segfault while tracing mlock/mprotect tests"
2380 )]
2381 #[test]
2382 fn test_mprotect_handles_exact_page_slice() {
2383 let pagesize = *PAGESIZE;
2384 let mut vec = HeapBytes::default();
2385 vec.resize(pagesize, 1);
2386
2387 dryoc_mprotect_readonly(vec.as_slice()).expect("readonly mprotect failed");
2388 dryoc_mprotect_readwrite(vec.as_slice()).expect("readwrite mprotect failed");
2389 vec[0] = 2;
2390 vec[pagesize - 1] = 3;
2391
2392 assert_eq!(vec[0], 2);
2393 assert_eq!(vec[pagesize - 1], 3);
2394 }
2395
2396 #[cfg(unix)]
2397 #[cfg_attr(
2398 tarpaulin,
2399 ignore = "tarpaulin can segfault while tracing mlock/mprotect tests"
2400 )]
2401 #[test]
2402 fn test_mprotect_noaccess_covers_page_boundary_tail() {
2403 let pagesize = *PAGESIZE;
2404 let mut vec = HeapBytes::default();
2405 vec.resize(pagesize + 1, 0);
2406
2407 dryoc_mprotect_noaccess(vec.as_slice()).expect("noaccess mprotect failed");
2408
2409 let child = unsafe { libc::fork() };
2410 assert!(child >= 0, "fork failed");
2411
2412 if child == 0 {
2413 let tail = unsafe { vec.as_slice().as_ptr().add(pagesize) as *mut u8 };
2414 unsafe {
2415 std::ptr::write_volatile(tail, 1);
2416 libc::_exit(0);
2417 }
2418 }
2419
2420 let mut status = 0;
2421 let wait_ret = unsafe { libc::waitpid(child, &mut status, 0) };
2422 dryoc_mprotect_readwrite(vec.as_slice()).expect("readwrite mprotect failed");
2423
2424 assert_eq!(wait_ret, child);
2425 assert!(
2426 libc::WIFSIGNALED(status),
2427 "child unexpectedly wrote to protected tail page"
2428 );
2429 }
2430
2431 }