Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial formal verification results #69

Merged
merged 18 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ jobs:
run: eval $(opam env) && REFINEDRUST_ROOT=$PWD/verification/refinedrust ./verification/refinedrust/scripts/setup-coq.sh
- name: Install RefinedRust type system
run: eval $(opam env) && REFINEDRUST_ROOT=$PWD/verification/refinedrust ./verification/refinedrust/scripts/install-typesystem.sh
- name: Install RefinedRust stdlib
run: eval $(opam env) && REFINEDRUST_ROOT=$PWD/verification/refinedrust ./verification/refinedrust/scripts/install-stdlib.sh
- name: Generate stdlib metadata
run: eval $(opam env) && make -C verification/refinedrust/stdlib generate_stdlib
- name: Exclude RefinedRust from dune build
run: echo "(dirs :standard \ generated_code.bak refinedrust)" > verification/dune
- name: install build dependencies
run: sudo apt -qq -y install wget autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
- name: install OpenSBI dependencies
Expand All @@ -54,7 +60,5 @@ jobs:
run: source "$HOME/.cargo/env" && eval $(opam env) && make setup
- name: make devtools
run: source "$HOME/.cargo/env" && eval $(opam env) && make devtools
- name: Translate specified files using RefinedRust
- name: Translate specified files using RefinedRust and check proofs
run: source "$HOME/.cargo/env" && eval $(opam env) && make verify
- name: Check proofs
run: eval $(opam env) && cd verification && dune build
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ tools: setup
cp -rf $(TOOLS_SOURCE_DIR)/* $(TOOLS_WORK_DIR)

verify:
rm -rf $(ACE_DIR)/security_monitor/verify/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we are veriyfing the security monitor implementation, we might also move the entire /verification/ directory under /security-monitor/verification/, if you think it helps with relative addressing etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it makes things a bit easier, but probably does not make much of a difference.
Would you prefer to put it in security-monitor/verification? If so, I can change it.
Alternatively, we can also decide to change the location in the future.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tagree with putting the verification in security-monitor/verification because this location reinforces the point that the verification is part of the project.

PATH="$(RISCV_GNU_TOOLCHAIN_WORK_DIR)/bin:$(PATH)" ACE_DIR=$(ACE_DIR) LINUX_IMAGE=$(LINUX_IMAGE) CROSS_COMPILE=$(CROSS_COMPILE) PLATFORM_RISCV_XLEN=$(PLATFORM_RISCV_XLEN) PLATFORM_RISCV_ISA=$(PLATFORM_RISCV_ISA) PLATFORM_RISCV_ABI=$(PLATFORM_RISCV_ABI) $(MAKE) -C verification verify

clean:
Expand Down
7 changes: 5 additions & 2 deletions security-monitor/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ build: opensbi_bindings fmt
cp $(SM_WORK_DIR)/$(CHAIN)/release/$(EXEC_NAME) $(SM_WORK_DIR)/ ; \
rm -rf $(OPENSBI_WORK_DIR)/

refinedrust: build
RUSTFLAGS='$(RUSTFLAGS)' CARGO_TARGET_DIR=$(SM_WORK_DIR) INSTALL_DIR=$(ACE_DIR) $(CARGO) refinedrust $(RELEASE) $(TARGET) --features verbose
refinedrust: opensbi_bindings
echo "Generating RefinedRust translation" ;\
mkdir -p $(SM_WORK_DIR) ; \
RUSTFLAGS='$(RUSTFLAGS)' CARGO_TARGET_DIR=$(SM_WORK_DIR) INSTALL_DIR=$(ACE_DIR) $(CARGO) refinedrust $(RELEASE) $(TARGET) --features verbose ; \
rm -rf $(OPENSBI_WORK_DIR)/

debug: opensbi_bindings
echo "Compiling the security monitor in DEBUG mode" ;\
Expand Down
12 changes: 12 additions & 0 deletions security-monitor/rust-crates/pointers_utility/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
#![no_main]
#![feature(pointer_byte_offsets)]

// used for RefinedRust annotations
#![feature(register_tool)]
#![register_tool(rr)]
#![feature(custom_inner_attributes)]
#![rr::coq_prefix("ace_ptr")]


mod error;
use core::mem::size_of;
pub use crate::error::PointerError;
Expand All @@ -27,6 +34,11 @@ pub fn ptr_align(pointer: *mut usize, align_in_bytes: usize, owned_region_end: *
/// the one-past-the-end address. The returned pointer is guaranteed to be valid for accesses
/// of size one, if the original pointer is valid. Additional checks are required for making
/// larger memory accesses.
#[rr::skip]
#[rr::params("l", "off", "lmax")]
#[rr::args("l", "off", "lmax")]
#[rr::requires("⌜l.2 + off < lmax.2⌝")]
#[rr::returns("Ok(l +ₗ off)")]
pub fn ptr_byte_add_mut(
pointer: *mut usize, offset_in_bytes: usize, owned_region_end: *const usize,
) -> Result<*mut usize, PointerError> {
Expand Down
31 changes: 31 additions & 0 deletions security-monitor/src/core/architecture/riscv/mmu/page_size.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
// SPDX-FileCopyrightText: 2023 IBM Corporation
// SPDX-FileContributor: Wojciech Ozga <woz@zurich.ibm.com>, IBM Research - Zurich
// SPDX-License-Identifier: Apache-2.0
#![rr::import("ace.theories.page_allocator", "page")]
#![rr::include("option")]

// The order of page size in this enum must follow the increasing sizes of page to guarantee that the Ord/PartialOrd are correctly derived
// for the `PageSize`.
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[rr::refined_by("page_size")]
pub enum PageSize {
#[rr::pattern("Size4KiB")]
Size4KiB,
/// 16KiB page is used by G-stage root page table in RISC-V
#[rr::pattern("Size16KiB")]
Size16KiB,
#[rr::pattern("Size2MiB")]
Size2MiB,
#[rr::pattern("Size1GiB")]
Size1GiB,
#[rr::pattern("Size512GiB")]
Size512GiB,
#[rr::pattern("Size128TiB")]
Size128TiB,
}

impl PageSize {
// Usually there are 512 pages of size x that can fit in a single page of size y, where y is next page size larger than x (e.g., 2MiB
// and 4KiB).
pub const TYPICAL_NUMBER_OF_PAGES_INSIDE_LARGER_PAGE: usize = 512;

#[rr::trust_me]
#[rr::params("x")]
#[rr::args("#x")]
#[rr::returns("page_size_in_bytes_Z x")]
pub fn in_bytes(&self) -> usize {
match self {
PageSize::Size128TiB => 8 * 512 * 512 * 512 * 512 * 256,
Expand All @@ -28,6 +45,10 @@ impl PageSize {
}
}

#[rr::trust_me]
#[rr::params("x")]
#[rr::args("#x")]
#[rr::returns("<#>@{option} page_size_smaller x")]
pub fn smaller(&self) -> Option<PageSize> {
match self {
PageSize::Size128TiB => Some(PageSize::Size512GiB),
Expand All @@ -39,6 +60,10 @@ impl PageSize {
}
}

#[rr::trust_me]
#[rr::params("x")]
#[rr::args("#x")]
#[rr::returns("<#>@{option} page_size_larger x")]
pub fn larger(&self) -> Option<PageSize> {
match self {
PageSize::Size128TiB => None,
Expand All @@ -50,6 +75,10 @@ impl PageSize {
}
}

#[rr::trust_me]
#[rr::params("x")]
#[rr::args("#x")]
#[rr::returns("page_size_multiplier x")]
pub fn number_of_smaller_pages(&self) -> usize {
match self {
PageSize::Size128TiB => 256,
Expand All @@ -61,10 +90,12 @@ impl PageSize {
}
}

#[rr::returns("Size128TiB")]
pub fn largest() -> PageSize {
PageSize::Size128TiB
}

#[rr::returns("Size4KiB")]
pub fn smallest() -> PageSize {
PageSize::Size4KiB
}
Expand Down
65 changes: 65 additions & 0 deletions security-monitor/src/core/architecture/riscv/mmu/page_table.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2023 IBM Corporation
// SPDX-FileContributor: Wojciech Ozga <woz@zurich.ibm.com>, IBM Research - Zurich
// SPDX-License-Identifier: Apache-2.0
#![rr::import("ace.theories.page_table", "page_table")]
use crate::core::architecture::mmu::page_table_entry::{LogicalPageTableEntry, PageTableEntry};
use crate::core::architecture::mmu::page_table_level::PageTableLevel;
use crate::core::architecture::mmu::paging_system::PagingSystem;
Expand All @@ -26,16 +27,34 @@ use sha2::digest::crypto_common::generic_array::GenericArray;
///
/// During the execution of a confidential hart, MMU might traverse the serialized representation. Our changes to this representation must
/// be atomic, so that MMU always reads a valid configuration.
///
/// Model: We model the page table by a `page_table_tree` which captures the inherent tree
/// structure.
#[rr::refined_by("pt_logical" : "page_table_tree")]
/// Invariant: We assert that there exists a serialized byte-level representation of the page table
/// tree in the form of a page.
#[rr::exists("pt_byte" : "page")]
#[rr::invariant("is_byte_level_representation pt_logical p_byte")]
pub struct PageTable {
#[rr::field("pt_get_level pt_logical")]
level: PageTableLevel,
#[rr::field("pt_get_system pt_logical")]
paging_system: PagingSystem,
/// Serialized representation stores the architecture-specific, binary representation of the page table configuration that is read by
/// the MMU.
#[rr::field("pt_byte")]
serialized_representation: Page<Allocated>,
/// Logical representation stores a strongly typed page table configuration used by security monitor.
#[rr::field("<#> (pt_get_entries pt_logical)")]
logical_representation: Vec<LogicalPageTableEntry>,
}

/// Verification: what do we want to prove?
/// - the page table is structured according to RISC-V spec
/// - the page table has unique ownership over all "reachable" memory
/// - copying a page table from non-confidential memory only reads from non-confidential memory
/// - if input to copy_.. is not a valid page table, fail correctly
#[rr::skip]
impl PageTable {
/// This functions copies recursively page table structure from non-confidential memory to confidential memory. It
/// allocated a page in confidential memory for every page table. After this function executes, a valid page table
Expand All @@ -44,6 +63,12 @@ impl PageTable {
/// If the input page table configuration has two page table entries that point to the same page, then the copied page table
/// configuration will have two page table entries pointing to different pages of the same content.
///
/// # Safety
///
/// This function expects a page table structure at the given `address` and will dereference
/// this pointer and further pointers parsed from the page table structure, as long as they are
/// in non-confidential memory.
///
/// # Guarantees
///
/// The copied page table configuration:
Expand All @@ -53,6 +78,30 @@ impl PageTable {
/// * all `pointer` page table entries point to another page table belonging to the same page table configuration.
///
/// This is a recursive function, which deepest execution is not larger than the number of paging system levels.
#[rr::params("l_nonconf", "ps", "level")]
#[rr::args("l_nonconf", "ps", "level")]
/// Precondition: We require the permission to copy from arbitrary non-confidential memory.
/// Note that this implicitly specifies that we do not read from confidential memory.
/// TODO: might need atomicity?
#[rr::requires(#iris "permission_to_read_from_nonconfidential_mem")]
// TODO: want to prove eventually
//#[rr::ensures("value_has_security_level Hypervisor x")]
// eventually: promote x from Hypervisor to confidential_vm_{new_id}
#[rr::exists("x" : "result page_table_tree core_error_Error")]
#[rr::returns("<#>@{result} x")]
/* Alternative specification:
// We won't need this for security, but if we were to trust the hypervisor, we could
// prove this specification.
//
// SPEC 2:
// For functional correctness (trusting the hypervisor):
#[rr::params("l_nonconf", "ps", "level" : "nat", "pt" : "page_table_tree")]
#[rr::args("l_nonconf", "ps", "level")]
#[rr::requires(#iris "address_has_valid_page_table l_nonconf pt")]
#[rr::exists("pt'")]
#[rr::ensures("related_page_tables pt pt'")]
#[rr::returns("#Ok(pt')")] // (or out of memory)
*/
pub fn copy_from_non_confidential_memory(
address: NonConfidentialMemoryAddress, paging_system: PagingSystem, level: PageTableLevel,
) -> Result<Self, Error> {
Expand Down Expand Up @@ -91,6 +140,11 @@ impl PageTable {

/// Creates an empty page table for the given page table level. Returns error if there is not enough memory to allocate this data
/// structure.
#[rr::params("system", "level")]
#[rr::args("system", "level")]
#[rr::exists("res")]
#[rr::returns("<#>@{result} res")]
#[rr::returns("if_Ok res (λ tree, tree = make_empty_page_tree system level)")]
pub fn empty(paging_system: PagingSystem, level: PageTableLevel) -> Result<Self, Error> {
let serialized_representation = PageAllocator::acquire_page(paging_system.memory_page_size(level))?.zeroize();
let number_of_entries = serialized_representation.size().in_bytes() / paging_system.entry_size();
Expand Down Expand Up @@ -188,6 +242,9 @@ impl PageTable {
}

/// Returns the physical address in confidential memory of the page table configuration.
#[rr::params("pt")]
#[rr::args("#pt")]
#[rr::returns("pt_get_serialized_loc pt")]
pub fn address(&self) -> usize {
self.serialized_representation.start_address()
}
Expand All @@ -197,6 +254,12 @@ impl PageTable {
}

/// Set a new page table entry at the given index, replacing whatever was there before.
#[rr::params("pt", "γ", "vpn", "pte")]
#[rr::args("(#pt, γ)", "vpn", "pte")]
/// Precondition: The vpn is valid for the number of entries of this page table.
#[rr::requires("vpn < pt_number_of_entries pt")]
/// Postcondition: The entry has been set correctly.
#[rr::oberve("γ": "pt_set_entry pt vpn pte")]
fn set_entry(&mut self, virtual_page_number: usize, entry: LogicalPageTableEntry) {
self.serialized_representation.write(self.paging_system.entry_size() * virtual_page_number, entry.serialize()).unwrap();
let entry_to_remove = core::mem::replace(&mut self.logical_representation[virtual_page_number], entry);
Expand All @@ -206,6 +269,8 @@ impl PageTable {
}

/// Recursively clears the entire page table configuration, releasing all pages to the PageAllocator.
#[rr::params("x")]
#[rr::args("x")]
pub fn deallocate(mut self) {
let mut pages = Vec::with_capacity(self.logical_representation.len() + 1);
pages.push(self.serialized_representation.deallocate());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2023 IBM Corporation
// SPDX-FileContributor: Wojciech Ozga <woz@zurich.ibm.com>, IBM Research - Zurich
// SPDX-License-Identifier: Apache-2.0
#![rr::import("ace.theories.page_table", "page_table")]
use super::specification::*;
use crate::core::architecture::mmu::page_table::PageTable;
use crate::core::architecture::SharedPage;
Expand All @@ -11,10 +12,18 @@ use alloc::boxed::Box;

/// Logical page table entry contains variants specific to the security monitor architecture. These new variants distinguish among certain
/// types (e.g., shared page, confidential data page) that are not covered by the general RISC-V specification.
#[rr::refined_by("logical_page_table_entry")]
pub(super) enum LogicalPageTableEntry {
#[rr::pattern("PointerToNextPageTable" $ "next", "conf")]
#[rr::refinement("-[ #(#next); #conf]")]
PointerToNextPageTable(Box<PageTable>),
#[rr::pattern("PageWithConfidentialVmData" $ "p", "conf", "perm")]
#[rr::refinement("-[ #(#p); #conf; #perm]")]
PageWithConfidentialVmData(Box<Page<Allocated>>),
#[rr::pattern("PageSharedWithHypervisor" $ "sp", "conf", "perm")]
#[rr::refinement("-[ #sp; #conf; #perm]")]
PageSharedWithHypervisor(SharedPage),
#[rr::pattern("UnmappedPage")]
NotMapped,
}

Expand Down Expand Up @@ -45,9 +54,15 @@ impl LogicalPageTableEntry {
}

/// Page table entry corresponds to entires defined by the RISC-V spec.
#[rr::refined_by("page_table_entry")]
pub(super) enum PageTableEntry {
#[rr::pattern("UnmappedPTE")]
NotMapped,
#[rr::pattern("NextPTE" $ "p")]
#[rr::refinement("-[ #p]")]
PointerToNextPageTable(*mut usize),
#[rr::pattern("DataPTE" $ "p")]
#[rr::refinement("-[ #p]")]
PointerToDataPage(*mut usize),
}

Expand All @@ -63,6 +78,7 @@ impl PageTableEntry {
/// Decodes a raw pointer from the page table entry. It is up to the user to decide how to deal with this pointer and check if it is
/// valid and is in confidential or non-confidential memory.
pub fn decode_pointer(raw_entry: usize) -> *mut usize {
// TODO: think how we can justify the integer-pointer cast
((raw_entry & !CONFIGURATION_BIT_MASK) << ADDRESS_SHIFT) as *mut usize
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@
// SPDX-License-Identifier: Apache-2.0

#[derive(Copy, Clone, Debug, PartialEq)]
#[rr::refined_by("page_table_level")]
pub enum PageTableLevel {
#[rr::pattern("PTLevel5")]
Level5,
#[rr::pattern("PTLevel4")]
Level4,
#[rr::pattern("PTLevel3")]
Level3,
#[rr::pattern("PTLevel2")]
Level2,
#[rr::pattern("PTLevel1")]
Level1,
}

impl PageTableLevel {
#[rr::trust_me]
#[rr::params("x")]
#[rr::args("#x")]
#[rr::returns("<#>@{option} (page_table_level_lower x)")]
pub fn lower(&self) -> Option<Self> {
match self {
Self::Level5 => Some(Self::Level4),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use crate::core::memory_layout::ConfidentialVmPhysicalAddress;
// TODO: add more 2nd-level paging systems corresponding to 3 and 4 level page
// tables.
#[derive(Debug, Copy, Clone)]
#[rr::refined_by("paging_system")]
pub enum PagingSystem {
#[rr::pattern("Sv57x4")]
Sv57x4,
}

Expand All @@ -26,6 +28,10 @@ impl PagingSystem {
}
}

#[rr::skip]
#[rr::params("system")]
#[rr::args("#system")]
#[rr::returns("paging_system_highest_level system")]
pub fn levels(&self) -> PageTableLevel {
match self {
PagingSystem::Sv57x4 => PageTableLevel::Level5,
Expand Down
Loading
Loading