-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Prevent unsoundness in environments with broken madvise(MADV_DONTNEED)
#11722
Changes from 1 commit
d6e2395
d3dce78
0c1bbf9
2730952
8d12533
3738b04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
madvise(MADV_DONTNEED)
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,7 @@ | |
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
|
||
| use crate::runtime::StoreData; | ||
| use crate::{runtime::StoreData, InstantiationStrategy}; | ||
| use sc_executor_common::{ | ||
| error::{Error, Result}, | ||
| util::checked_range, | ||
|
|
@@ -98,3 +98,91 @@ pub(crate) fn write_memory_from( | |
| memory[range].copy_from_slice(data); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Checks whether the `madvise(MADV_DONTNEED)` works as expected. | ||
| /// | ||
| /// In certain environments (e.g. when running under the QEMU user-mode emulator) | ||
| /// this syscall is broken. | ||
| #[cfg(target_os = "linux")] | ||
| fn is_madvise_working() -> std::result::Result<bool, String> { | ||
| unsafe { | ||
| // Allocate two memory pages. | ||
| let pointer = rustix::mm::mmap_anonymous( | ||
| std::ptr::null_mut(), | ||
| 2 * 4096, | ||
koute marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| rustix::mm::ProtFlags::READ | rustix::mm::ProtFlags::WRITE, | ||
| rustix::mm::MapFlags::PRIVATE, | ||
| ) | ||
| .map_err(|error| format!("mmap failed: {}", error))?; | ||
|
|
||
| // Dirty them both. | ||
| std::ptr::write_volatile(pointer.cast::<u8>(), b'A'); | ||
| std::ptr::write_volatile(pointer.cast::<u8>().add(4096), b'B'); | ||
|
|
||
| // Clear the first page. | ||
| let result_madvise = rustix::mm::madvise(pointer, 4096, rustix::mm::Advice::LinuxDontNeed) | ||
| .map_err(|error| format!("madvise failed: {}", error)); | ||
|
|
||
| // Fetch the values. | ||
| let value_1 = std::ptr::read_volatile(pointer.cast::<u8>()); | ||
| let value_2 = std::ptr::read_volatile(pointer.cast::<u8>().add(4096)); | ||
|
|
||
| let result_munmap = rustix::mm::munmap(pointer, 2 * 4096) | ||
| .map_err(|error| format!("munmap failed: {}", error)); | ||
|
|
||
| result_madvise?; | ||
| result_munmap?; | ||
|
|
||
| // Verify that the first page was cleared, while the second one was not. | ||
| Ok(value_1 == 0 && value_2 == b'B') | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| #[cfg(target_os = "linux")] | ||
| #[test] | ||
| fn test_is_madvise_working() { | ||
| assert_eq!(is_madvise_working(), Ok(true)); | ||
| } | ||
|
||
|
|
||
| /// Checks whether a given instantiation strategy can be safely used, and replaces | ||
| /// it with a slower (but sound) alternative if it isn't. | ||
| #[cfg(target_os = "linux")] | ||
| pub(crate) fn replace_strategy_if_broken(strategy: &mut InstantiationStrategy) { | ||
| let replacement_strategy = match *strategy { | ||
| // These strategies don't need working `madvise`. | ||
| InstantiationStrategy::Pooling | InstantiationStrategy::RecreateInstance => return, | ||
|
|
||
| // These strategies require a working `madvise` to be sound. | ||
| InstantiationStrategy::PoolingCopyOnWrite => InstantiationStrategy::Pooling, | ||
| InstantiationStrategy::RecreateInstanceCopyOnWrite | | ||
| InstantiationStrategy::LegacyInstanceReuse => InstantiationStrategy::RecreateInstance, | ||
| }; | ||
|
|
||
| use once_cell::sync::OnceCell; | ||
| static IS_OK: OnceCell<bool> = OnceCell::new(); | ||
|
|
||
| let is_ok = IS_OK.get_or_init(|| { | ||
| let is_ok = match is_madvise_working() { | ||
| Ok(is_ok) => is_ok, | ||
| Err(error) => { | ||
| // This should never happen. | ||
| log::warn!("Failed to check whether `madvise(MADV_DONTNEED)` works: {}", error); | ||
| false | ||
| } | ||
| }; | ||
|
|
||
| if !is_ok { | ||
| log::warn!("You're running on a system with a broken `madvise(MADV_DONTNEED)` implementation. This will result in lower performance."); | ||
| } | ||
|
|
||
| is_ok | ||
| }); | ||
|
|
||
| if !is_ok { | ||
| *strategy = replacement_strategy; | ||
| } | ||
| } | ||
|
|
||
| #[cfg(not(target_os = "linux"))] | ||
| pub(crate) fn replace_strategy_if_broken(_: &mut InstantiationStrategy) {} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not do this call in
common_config?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered it, but honestly that's kind of messy, since the
instantiation_strategyis also used outside ofcommon_config, socommon_configwould have to modify thesemanticswhich is passed to it, and if it did that then instead of doing only one thing (creating a config for the engine) it'd be doing two things (creating a config and replacing the strategy), so I felt it'd be cleaner to just do it outside ofcommon_configand have it done explicitly.