diff --git a/Makefile b/Makefile index b9f0699..9df92d4 100644 --- a/Makefile +++ b/Makefile @@ -29,11 +29,27 @@ clippy: check: fmt-check clippy .PHONY: test -test: - @echo "Building and running unit tests..." +test: enclave-test untrusted-test + @echo "All tests completed successfully!" + +.PHONY: enclave-test +enclave-test: + @echo "Building and running enclave unit tests..." @cd unit-test && make clean all @cd unit-test/bin && ./app +.PHONY: untrusted-test +untrusted-test: + @echo "Running untrusted crate tests..." + @echo "Testing sgx-types..." + @cd sgx-types && cargo test --features urts + @echo "Testing sgx-urts..." + @cd sgx-urts && cargo test --features simulate_utils + @echo "Testing sgx-build..." + @cd sgx-build && cargo test + @echo "Testing cargo-sgx..." + @cd cargo-sgx && cargo test + .PHONY: toml-fmt toml-fmt: @echo "Formatting TOML files..." diff --git a/samples/hello-rust/app/src/main.rs b/samples/hello-rust/app/src/main.rs index ce37997..d33770e 100644 --- a/samples/hello-rust/app/src/main.rs +++ b/samples/hello-rust/app/src/main.rs @@ -36,13 +36,7 @@ extern "C" { fn init_enclave() -> SgxResult { let mut launch_token: sgx_launch_token_t = [0; 1024]; let mut launch_token_updated: i32 = 0; - let debug = match env::var("SGX_DEBUG") { - Ok(val) => match val.as_str() { - "1" => 1, - _ => 0, - }, - Err(_) => 0, - }; + let debug = env::var("SGX_DEBUG").unwrap_or_default() == "1"; let mut misc_attr = sgx_misc_attribute_t { secs_attr: sgx_attributes_t { flags: 0, xfrm: 0 }, misc_select: 0, @@ -50,7 +44,7 @@ fn init_enclave() -> SgxResult { // call sgx_create_enclave to initialize an enclave instance SgxEnclave::create( ENCLAVE_FILE, - debug, + debug.into(), &mut launch_token, &mut launch_token_updated, &mut misc_attr, diff --git a/sgx-ert/Cargo.toml b/sgx-ert/Cargo.toml index 3edb6e3..a9de34a 100644 --- a/sgx-ert/Cargo.toml +++ b/sgx-ert/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "sgx-ert" -version = "1.2.0" -authors = ["The SGX-SDK-RS Authors"] -repository = "https://github.com/datachainlab/sgx-sdk-rs" -license = "Apache-2.0" -description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." -edition = "2021" +name = "sgx-ert" +version = "1.2.0" +authors = ["The SGX-SDK-RS Authors"] +repository = "https://github.com/datachainlab/sgx-sdk-rs" +license = "Apache-2.0" +description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." +edition = "2021" [lib] name = "sgx_ert" diff --git a/sgx-tcrypto/Cargo.toml b/sgx-tcrypto/Cargo.toml index aa39b57..ec50081 100644 --- a/sgx-tcrypto/Cargo.toml +++ b/sgx-tcrypto/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "sgx-tcrypto" -version = "1.2.0" -authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] -repository = "https://github.com/datachainlab/sgx-sdk-rs" -license = "Apache-2.0" -description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." -edition = "2021" +name = "sgx-tcrypto" +version = "1.2.0" +authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] +repository = "https://github.com/datachainlab/sgx-sdk-rs" +license = "Apache-2.0" +description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." +edition = "2021" [lib] name = "sgx_tcrypto" diff --git a/sgx-trts/Cargo.toml b/sgx-trts/Cargo.toml index 7ae3379..a0fb986 100644 --- a/sgx-trts/Cargo.toml +++ b/sgx-trts/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "sgx-trts" -version = "1.2.0" -authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] -repository = "https://github.com/datachainlab/sgx-sdk-rs" -license = "Apache-2.0" -description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." -edition = "2021" +name = "sgx-trts" +version = "1.2.0" +authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] +repository = "https://github.com/datachainlab/sgx-sdk-rs" +license = "Apache-2.0" +description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." +edition = "2021" [lib] name = "sgx_trts" diff --git a/sgx-tse/Cargo.toml b/sgx-tse/Cargo.toml index 93e332d..05e4e09 100644 --- a/sgx-tse/Cargo.toml +++ b/sgx-tse/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "sgx-tse" -version = "1.2.0" -authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] -repository = "https://github.com/datachainlab/sgx-sdk-rs" -license = "Apache-2.0" -description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." -edition = "2021" +name = "sgx-tse" +version = "1.2.0" +authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] +repository = "https://github.com/datachainlab/sgx-sdk-rs" +license = "Apache-2.0" +description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." +edition = "2021" [lib] name = "sgx_tse" diff --git a/sgx-tseal/Cargo.toml b/sgx-tseal/Cargo.toml index ebbd95e..fa6cd4b 100644 --- a/sgx-tseal/Cargo.toml +++ b/sgx-tseal/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "sgx-tseal" -version = "1.2.0" -authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] -repository = "https://github.com/datachainlab/sgx-sdk-rs" -license = "Apache-2.0" -description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." -edition = "2021" +name = "sgx-tseal" +version = "1.2.0" +authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] +repository = "https://github.com/datachainlab/sgx-sdk-rs" +license = "Apache-2.0" +description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." +edition = "2021" [lib] name = "sgx_tseal" diff --git a/sgx-types/Cargo.toml b/sgx-types/Cargo.toml index 13097de..79264ed 100644 --- a/sgx-types/Cargo.toml +++ b/sgx-types/Cargo.toml @@ -1,11 +1,11 @@ [package] -authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] -name = "sgx-types" -version = "1.2.0" -repository = "https://github.com/datachainlab/sgx-sdk-rs" -license = "Apache-2.0" -description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." -edition = "2021" +authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] +name = "sgx-types" +version = "1.2.0" +repository = "https://github.com/datachainlab/sgx-sdk-rs" +license = "Apache-2.0" +description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." +edition = "2021" [lib] name = "sgx_types" diff --git a/sgx-urts/Cargo.toml b/sgx-urts/Cargo.toml index 634e731..ff545d9 100644 --- a/sgx-urts/Cargo.toml +++ b/sgx-urts/Cargo.toml @@ -1,21 +1,25 @@ [package] -name = "sgx-urts" -version = "1.2.0" -authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] -repository = "https://github.com/datachainlab/sgx-sdk-rs" -license = "Apache-2.0" -description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." -edition = "2021" +name = "sgx-urts" +version = "1.2.0" +authors = ["The Teaclave Authors", "The SGX-SDK-RS Authors"] +repository = "https://github.com/datachainlab/sgx-sdk-rs" +license = "Apache-2.0" +description = "Rust SGX SDK provides the ability to write Intel SGX applications in Rust Programming Language." +edition = "2021" [lib] name = "sgx_urts" crate-type = ["rlib"] [features] -default = [] -global_init = ["global_exit"] -global_exit = ["global_init"] +default = [] +global_init = ["global_exit"] +global_exit = ["global_init"] +simulate_utils = ["tracing", "object", "iced-x86"] [dependencies] sgx-types = { path = "../sgx-types", default-features = false, features = ["urts"] } -libc = "0.2" +tracing = { version = "0.1", optional = true } +libc = { version = "0.2" } +object = { version = "0.37", optional = true } +iced-x86 = { version = "1.21", optional = true } diff --git a/sgx-urts/src/lib.rs b/sgx-urts/src/lib.rs index 56d1f99..a4e8aed 100644 --- a/sgx-urts/src/lib.rs +++ b/sgx-urts/src/lib.rs @@ -28,6 +28,8 @@ pub mod net; pub mod pipe; pub mod process; pub mod signal; +#[cfg(feature = "simulate_utils")] +pub mod simulate; pub mod socket; pub mod sys; pub mod thread; diff --git a/sgx-urts/src/simulate.rs b/sgx-urts/src/simulate.rs new file mode 100644 index 0000000..1bcf909 --- /dev/null +++ b/sgx-urts/src/simulate.rs @@ -0,0 +1,400 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use crate::SgxEnclave; +use iced_x86::{Code, Decoder, DecoderOptions}; +use object::elf::PF_X; +use object::{Object, ObjectSegment}; +use sgx_types::*; +use std::ptr; +use tracing::{debug, info}; + +/// Patch prohibited instructions in enclave binary +pub fn patch_enclave_binary(binary_data: &[u8]) -> Result, String> { + info!("Loading and patching enclave binary"); + + // Create a mutable copy + let mut patched_binary = binary_data.to_vec(); + + // Parse the binary + let obj = + object::File::parse(binary_data).map_err(|e| format!("Failed to parse binary: {e}"))?; + + // Check if this is an ELF binary - only ELF is supported for now + let format = obj.format(); + if format != object::BinaryFormat::Elf { + return Err(format!( + "Unsupported binary format: {format:?}. Only ELF binaries are supported for patching.", + )); + } + + let mut total_patches = 0; + let mut cpuid_count = 0; + let mut syscall_count = 0; + let mut sysenter_count = 0; + let mut int80_count = 0; + + // Scan all executable segments + for segment in obj.segments() { + // Check if segment is executable + let flags = segment.flags(); + if match flags { + object::SegmentFlags::Elf { p_flags } => p_flags & PF_X == 0, + _ => { + // This should not happen as we already checked for ELF format + debug!("Unexpected segment flags type in ELF binary"); + true + } + } { + continue; + } + + let (file_offset, file_size) = segment.file_range(); + let segment_data = &binary_data[file_offset as usize..(file_offset + file_size) as usize]; + let segment_address = segment.address(); + + debug!( + "Scanning executable segment at file offset {:#x}, virtual address {:#x}, size {:#x}", + file_offset, segment_address, file_size + ); + + // Create x86-64 decoder with the segment's virtual address + let mut decoder = Decoder::with_ip(64, segment_data, segment_address, DecoderOptions::NONE); + + // Decode all instructions in the segment + while decoder.can_decode() { + let instruction = decoder.decode(); + + // Determine if this is a prohibited instruction + let needs_patch = match instruction.code() { + Code::Cpuid => true, + Code::Syscall => true, + Code::Sysenter => true, + // For INT 0x80, we need to check the immediate value + Code::Int_imm8 => instruction.immediate8() == 0x80, + _ => false, + }; + + if !needs_patch { + continue; + } + + // Calculate file offset for this instruction + let instruction_rva = instruction.ip() - segment_address; + let file_position = file_offset as usize + instruction_rva as usize; + let instruction_len = instruction.len() as u8; + + debug!( + "Found {} at virtual address {:#x}, file offset {:#x}, length {} bytes", + match instruction.code() { + Code::Cpuid => "CPUID", + Code::Syscall => "SYSCALL", + Code::Sysenter => "SYSENTER", + Code::Int_imm8 if instruction.immediate8() == 0x80 => "INT 0x80", + _ => "UNKNOWN", + }, + instruction.ip(), + file_position, + instruction_len + ); + + // Patch the instruction: UD2 + NOPs + patched_binary[file_position] = 0x0F; // UD2 first byte + patched_binary[file_position + 1] = 0x0B; // UD2 second byte + + // Fill remaining bytes with NOPs + for i in 2..instruction_len as usize { + patched_binary[file_position + i] = 0x90; // NOP + } + + // Update counters + total_patches += 1; + match instruction.code() { + Code::Cpuid => cpuid_count += 1, + Code::Syscall => syscall_count += 1, + Code::Sysenter => sysenter_count += 1, + Code::Int_imm8 if instruction.immediate8() == 0x80 => int80_count += 1, + _ => {} + } + } + } + + info!("Patching completed. Total patches: {}", total_patches); + info!(" CPUID: {}", cpuid_count); + info!(" SYSCALL: {}", syscall_count); + info!(" SYSENTER: {}", sysenter_count); + info!(" INT 0x80: {}", int80_count); + + Ok(patched_binary) +} + +/// Create an enclave from a file with prohibited instructions patched to UD2 +pub fn create_patched_enclave( + enclave_file: &str, + debug: i32, + misc_attr: &mut sgx_misc_attribute_t, +) -> SgxResult { + // Read enclave binary + let binary_data = + std::fs::read(enclave_file).map_err(|_| sgx_status_t::SGX_ERROR_INVALID_ENCLAVE)?; + + // Use the buffer version + create_patched_enclave_from_buffer(&binary_data, debug, misc_attr) +} + +/// Create an enclave from a buffer with prohibited instructions patched to UD2 +pub fn create_patched_enclave_from_buffer( + enclave_buffer: &[u8], + debug: i32, + misc_attr: &mut sgx_misc_attribute_t, +) -> SgxResult { + // Patch prohibited instructions + let patched_binary = patch_enclave_binary(enclave_buffer) + .map_err(|_| sgx_status_t::SGX_ERROR_INVALID_ENCLAVE)?; + + // Create enclave from patched binary + SgxEnclave::create_from_buffer(&patched_binary, debug, misc_attr, 0, &[ptr::null(); 32]) +} + +#[cfg(test)] +mod tests { + use super::*; + + // Constants for test ELF layout + const TEST_ELF_TEXT_OFFSET: usize = 0x1000; + const TEST_ELF_DATA_OFFSET: usize = 0x2000; + + #[test] + fn test_patch_enclave_binary_basic() { + // Create a minimal ELF with executable segment containing prohibited instructions + let elf_binary = create_test_elf_with_code(&[ + 0x90, // NOP + 0x0F, 0xA2, // CPUID (2 bytes) + 0x90, // NOP + 0x0F, 0x05, // SYSCALL (2 bytes) + 0x90, // NOP + 0x0F, 0x34, // SYSENTER (2 bytes) + 0x90, // NOP + 0xCD, 0x80, // INT 0x80 (2 bytes) + 0x90, // NOP + 0xC3, // RET + ]); + + // Call patch_enclave_binary + let result = patch_enclave_binary(&elf_binary); + assert!(result.is_ok(), "Failed to patch binary: {:?}", result.err()); + + let patched = result.unwrap(); + + // Find the code section + let code_offset = TEST_ELF_TEXT_OFFSET; + + // Verify patches + assert_eq!(patched[code_offset], 0x90); // NOP unchanged + assert_eq!(patched[code_offset + 1], 0x0F); // CPUID -> UD2 first byte + assert_eq!(patched[code_offset + 2], 0x0B); // CPUID -> UD2 second byte + assert_eq!(patched[code_offset + 3], 0x90); // NOP unchanged + assert_eq!(patched[code_offset + 4], 0x0F); // SYSCALL -> UD2 first byte + assert_eq!(patched[code_offset + 5], 0x0B); // SYSCALL -> UD2 second byte + assert_eq!(patched[code_offset + 6], 0x90); // NOP unchanged + assert_eq!(patched[code_offset + 7], 0x0F); // SYSENTER -> UD2 first byte + assert_eq!(patched[code_offset + 8], 0x0B); // SYSENTER -> UD2 second byte + assert_eq!(patched[code_offset + 9], 0x90); // NOP unchanged + assert_eq!(patched[code_offset + 10], 0x0F); // INT 0x80 -> UD2 first byte + assert_eq!(patched[code_offset + 11], 0x0B); // INT 0x80 -> UD2 second byte + assert_eq!(patched[code_offset + 12], 0x90); // NOP unchanged + assert_eq!(patched[code_offset + 13], 0xC3); // RET unchanged + } + + #[test] + fn test_patch_enclave_binary_with_prefixes() { + // Test instructions with prefixes (different lengths) + let elf_binary = create_test_elf_with_code(&[ + // REX prefix + SYSCALL (3 bytes) + 0x48, 0x0F, 0x05, // REX.W SYSCALL + // Operand size prefix + SYSCALL (3 bytes) + 0x66, 0x0F, 0x05, // 66 SYSCALL + // REP prefix + CPUID (3 bytes) + 0xF3, 0x0F, 0xA2, // REP CPUID + // Multiple prefixes + SYSENTER (4 bytes) + 0x66, 0xF3, 0x0F, 0x34, // 66 REP SYSENTER + 0xC3, // RET + ]); + + let result = patch_enclave_binary(&elf_binary); + assert!(result.is_ok(), "Failed to patch binary: {:?}", result.err()); + + let patched = result.unwrap(); + let code_offset = TEST_ELF_TEXT_OFFSET; + + // Verify REX.W SYSCALL patch (3 bytes) + assert_eq!(patched[code_offset], 0x0F); // UD2 first byte + assert_eq!(patched[code_offset + 1], 0x0B); // UD2 second byte + assert_eq!(patched[code_offset + 2], 0x90); // NOP padding + + // Verify 66 SYSCALL patch (3 bytes) + assert_eq!(patched[code_offset + 3], 0x0F); // UD2 first byte + assert_eq!(patched[code_offset + 4], 0x0B); // UD2 second byte + assert_eq!(patched[code_offset + 5], 0x90); // NOP padding + + // Verify REP CPUID patch (3 bytes) + assert_eq!(patched[code_offset + 6], 0x0F); // UD2 first byte + assert_eq!(patched[code_offset + 7], 0x0B); // UD2 second byte + assert_eq!(patched[code_offset + 8], 0x90); // NOP padding + + // Verify 66 REP SYSENTER patch (4 bytes) + assert_eq!(patched[code_offset + 9], 0x0F); // UD2 first byte + assert_eq!(patched[code_offset + 10], 0x0B); // UD2 second byte + assert_eq!(patched[code_offset + 11], 0x90); // NOP padding + assert_eq!(patched[code_offset + 12], 0x90); // NOP padding + + // RET unchanged + assert_eq!(patched[code_offset + 13], 0xC3); + } + + #[test] + fn test_patch_enclave_binary_no_false_positives() { + // Test that we don't patch data that looks like instructions + let elf_binary = create_test_elf_with_mixed_sections( + &[ + // .text section + 0x90, // NOP + 0x0F, 0xA2, // CPUID (should be patched) + 0xC3, // RET + ], + &[ + // .data section (non-executable) + 0x0F, 0xA2, // Data that looks like CPUID (should NOT be patched) + 0x0F, 0x05, // Data that looks like SYSCALL (should NOT be patched) + 0x0F, 0x34, // Data that looks like SYSENTER (should NOT be patched) + 0xCD, 0x80, // Data that looks like INT 0x80 (should NOT be patched) + ], + ); + + let result = patch_enclave_binary(&elf_binary); + assert!(result.is_ok(), "Failed to patch binary: {:?}", result.err()); + + let patched = result.unwrap(); + let text_offset = TEST_ELF_TEXT_OFFSET; + let data_offset = TEST_ELF_DATA_OFFSET; + + // Verify .text section: CPUID should be patched + assert_eq!(patched[text_offset], 0x90); // NOP unchanged + assert_eq!(patched[text_offset + 1], 0x0F); // CPUID -> UD2 first byte + assert_eq!(patched[text_offset + 2], 0x0B); // CPUID -> UD2 second byte + assert_eq!(patched[text_offset + 3], 0xC3); // RET unchanged + + // Verify .data section: nothing should be patched + assert_eq!(patched[data_offset], 0x0F); // Data unchanged + assert_eq!(patched[data_offset + 1], 0xA2); // Data unchanged + assert_eq!(patched[data_offset + 2], 0x0F); // Data unchanged + assert_eq!(patched[data_offset + 3], 0x05); // Data unchanged + assert_eq!(patched[data_offset + 4], 0x0F); // Data unchanged + assert_eq!(patched[data_offset + 5], 0x34); // Data unchanged + assert_eq!(patched[data_offset + 6], 0xCD); // Data unchanged + assert_eq!(patched[data_offset + 7], 0x80); // Data unchanged + } + + #[test] + fn test_patch_enclave_binary_jump_table_no_false_positive() { + // Test that jump table entries are not mistaken for instructions + let elf_binary = create_test_elf_with_code(&[ + // A simple function with embedded data (simulating a jump table) + 0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00, // MOV RAX, [RIP+0] + 0xFF, 0xE0, // JMP RAX + // Data that looks like prohibited instructions but is actually jump addresses + 0x05, 0x0F, 0x00, 0x00, // Address that starts with 0x05, 0x0F (not SYSCALL) + 0xA2, 0x0F, 0x00, 0x00, // Address that starts with 0xA2, 0x0F (not CPUID) + // Real prohibited instruction after the data + 0x0F, 0xA2, // CPUID (should be patched) + 0xC3, // RET + ]); + + let result = patch_enclave_binary(&elf_binary); + assert!(result.is_ok(), "Failed to patch binary: {:?}", result.err()); + + let patched = result.unwrap(); + let code_offset = TEST_ELF_TEXT_OFFSET; + + // Verify that the jump table data is not patched + assert_eq!(patched[code_offset + 9], 0x05); // Data unchanged + assert_eq!(patched[code_offset + 10], 0x0F); // Data unchanged + assert_eq!(patched[code_offset + 13], 0xA2); // Data unchanged + assert_eq!(patched[code_offset + 14], 0x0F); // Data unchanged + + // Verify that the real CPUID instruction is patched + assert_eq!(patched[code_offset + 17], 0x0F); // CPUID -> UD2 first byte + assert_eq!(patched[code_offset + 18], 0x0B); // CPUID -> UD2 second byte + } + + // Helper functions for tests + fn create_test_elf_with_code(code: &[u8]) -> Vec { + create_test_elf_with_mixed_sections(code, &[]) + } + + fn create_test_elf_with_mixed_sections(text_content: &[u8], data_content: &[u8]) -> Vec { + // This creates a minimal ELF with proper program headers and sections + // The actual implementation would create a valid ELF structure + // For brevity, using a simplified version here + let total_size = 0x3000 + text_content.len().max(data_content.len()); + let mut elf = vec![0u8; total_size]; // Allocate space + + // ELF header + elf[0..8].copy_from_slice(&[0x7f, b'E', b'L', b'F', 0x02, 0x01, 0x01, 0x00]); + elf[0x10] = 0x03; // ET_DYN + elf[0x12] = 0x3e; // EM_X86_64 + elf[0x14] = 0x01; // EV_CURRENT + + // Program header offset + elf[0x20..0x28].copy_from_slice(&0x40u64.to_le_bytes()); + + // e_phentsize (size of program header entry) + elf[0x36..0x38].copy_from_slice(&0x38u16.to_le_bytes()); + + // e_phnum (number of program headers) + let phnum = if data_content.is_empty() { 1u16 } else { 2u16 }; + elf[0x38..0x3A].copy_from_slice(&phnum.to_le_bytes()); + + // Program header for executable segment at offset 0x40 + elf[0x40] = 0x01; // PT_LOAD + elf[0x44] = 0x05; // PF_X | PF_R (executable) + elf[0x48..0x50].copy_from_slice(&(TEST_ELF_TEXT_OFFSET as u64).to_le_bytes()); // offset + elf[0x50..0x58].copy_from_slice(&(TEST_ELF_TEXT_OFFSET as u64).to_le_bytes()); // vaddr + elf[0x60..0x68].copy_from_slice(&(text_content.len() as u64).to_le_bytes()); // filesz + elf[0x68..0x70].copy_from_slice(&(text_content.len() as u64).to_le_bytes()); // memsz + + // Copy text content + elf[TEST_ELF_TEXT_OFFSET..TEST_ELF_TEXT_OFFSET + text_content.len()] + .copy_from_slice(text_content); + + if !data_content.is_empty() { + // Program header for data segment at offset 0x80 + elf[0x80] = 0x01; // PT_LOAD + elf[0x84] = 0x06; // PF_R | PF_W (not executable) + elf[0x88..0x90].copy_from_slice(&(TEST_ELF_DATA_OFFSET as u64).to_le_bytes()); // offset + elf[0x90..0x98].copy_from_slice(&(TEST_ELF_DATA_OFFSET as u64).to_le_bytes()); // vaddr + elf[0xA0..0xA8].copy_from_slice(&(data_content.len() as u64).to_le_bytes()); // filesz + elf[0xA8..0xB0].copy_from_slice(&(data_content.len() as u64).to_le_bytes()); // memsz + + // Copy data content + elf[TEST_ELF_DATA_OFFSET..TEST_ELF_DATA_OFFSET + data_content.len()] + .copy_from_slice(data_content); + } + + elf + } +} diff --git a/unit-test/app/Cargo.toml b/unit-test/app/Cargo.toml index 077c576..d94294f 100644 --- a/unit-test/app/Cargo.toml +++ b/unit-test/app/Cargo.toml @@ -6,8 +6,11 @@ build = "build.rs" edition = "2021" [dependencies] -sgx-types = { path = "../../sgx-types", default-features = false, features = ["uae_service", "urts"] } -sgx-urts = { path = "../../sgx-urts" } +sgx-types = { path = "../../sgx-types", default-features = false, features = ["uae_service", "urts"] } +sgx-urts = { path = "../../sgx-urts", features = ["simulate_utils"] } +libc = "0.2" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } [build-dependencies] sgx-build = { path = "../../sgx-build" } diff --git a/unit-test/app/build.rs b/unit-test/app/build.rs index 85d8b50..bc7d5b6 100644 --- a/unit-test/app/build.rs +++ b/unit-test/app/build.rs @@ -1,4 +1,15 @@ fn main() { + // Check SGX_MODE environment variable at compile time + let sgx_mode = std::env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string()); + + // Set configuration based on SGX_MODE + println!("cargo:rustc-check-cfg=cfg(sgx_sim)"); + if sgx_mode == "SW" { + println!("cargo:rustc-cfg=sgx_sim"); + } + + println!("cargo:rerun-if-env-changed=SGX_MODE"); + sgx_build::SgxBuilder::new() .build_app("../enclave/Enclave.edl") .expect("Failed to build SGX app"); diff --git a/unit-test/app/src/main.rs b/unit-test/app/src/main.rs index c2ce2b6..3012b95 100644 --- a/unit-test/app/src/main.rs +++ b/unit-test/app/src/main.rs @@ -15,18 +15,100 @@ // specific language governing permissions and limitations // under the License.. +#[cfg(sgx_sim)] +use libc::{sigaction, siginfo_t, SA_NODEFER, SA_RESTART, SA_SIGINFO, SIGILL}; use sgx_types::*; use sgx_urts::SgxEnclave; use std::env; +#[cfg(sgx_sim)] +use std::mem; +#[cfg(sgx_sim)] +use std::ptr; use std::slice; use std::str; +use std::sync::atomic::{AtomicU32, Ordering}; static ENCLAVE_FILE: &str = "enclave.signed.so"; +// Static variable to track trapped instructions +#[cfg(sgx_sim)] +static PROHIBITED_INSTRUCTION_COUNT: AtomicU32 = AtomicU32::new(0); + extern "C" { fn test_main_entrance(eid: sgx_enclave_id_t, retval: *mut size_t) -> sgx_status_t; } +// Helper function to write log messages to stderr +#[cfg(sgx_sim)] +fn log_to_stderr(msg: &str) { + unsafe { + libc::write(2, msg.as_ptr() as *const libc::c_void, msg.len()); + } +} + +// SIGILL handler for UD2 traps +#[cfg(sgx_sim)] +extern "C" fn sigill_handler(_sig: libc::c_int, _info: *mut siginfo_t, context: *mut libc::c_void) { + // Extract RIP from context + let rip = unsafe { + let uc = context as *mut libc::ucontext_t; + let gregs = &(*uc).uc_mcontext.gregs; + gregs[libc::REG_RIP as usize] + }; + + log_to_stderr(&format!( + "[Unit Test] SIGILL handler called at RIP {rip:#x}\n" + )); + + // Check if it's UD2 (0x0F 0x0B) + let instruction_ptr = rip as *const u8; + let first_byte = unsafe { *instruction_ptr }; + let second_byte = unsafe { *(instruction_ptr.add(1)) }; + + if first_byte == 0x0F && second_byte == 0x0B { + // UD2 detected - this is one of our patched prohibited instructions + log_to_stderr(&format!( + "[Unit Test] Prohibited instruction detected at RIP {rip:#x}\n" + )); + + // Skip UD2 instruction (2 bytes) + unsafe { + let uc = context as *mut libc::ucontext_t; + let gregs = &mut (*uc).uc_mcontext.gregs; + gregs[libc::REG_RIP as usize] += 2; + } + + // Increment counter + let count = PROHIBITED_INSTRUCTION_COUNT.fetch_add(1, Ordering::SeqCst) + 1; + log_to_stderr(&format!( + "[Unit Test] Prohibited instruction count: {count}\n" + )); + } else { + log_to_stderr(&format!( + "[Unit Test] Non-UD2 instruction: {first_byte:#x} {second_byte:#x}\n" + )); + } +} + +/// Install SIGILL handler for unit tests +#[cfg(sgx_sim)] +fn install_sigill_handler() -> Result<(), String> { + // Install our handler + let mut sa: sigaction = unsafe { mem::zeroed() }; + sa.sa_sigaction = sigill_handler as usize; + sa.sa_flags = SA_SIGINFO | SA_NODEFER | SA_RESTART; + unsafe { + libc::sigemptyset(&mut sa.sa_mask); + + if sigaction(SIGILL, &sa, ptr::null_mut()) != 0 { + return Err("Failed to install SIGILL handler".to_string()); + } + } + + println!("[Unit Test] SIGILL handler installed successfully"); + Ok(()) +} + #[no_mangle] #[allow(clippy::missing_safety_doc)] pub unsafe extern "C" fn ocall_print_string(str_ptr: *const u8, str_len: usize) { @@ -36,30 +118,51 @@ pub unsafe extern "C" fn ocall_print_string(str_ptr: *const u8, str_len: usize) } fn init_enclave() -> SgxResult { - let mut launch_token: sgx_launch_token_t = [0; 1024]; - let mut launch_token_updated: i32 = 0; - let debug = match env::var("SGX_DEBUG") { - Ok(val) => match val.as_str() { - "1" => 1, - _ => 0, - }, - Err(_) => 0, - }; + let debug = env::var("SGX_DEBUG").unwrap_or_default() == "1"; let mut misc_attr = sgx_misc_attribute_t { secs_attr: sgx_attributes_t { flags: 0, xfrm: 0 }, misc_select: 0, }; - // call sgx_create_enclave to initialize an enclave instance - SgxEnclave::create( - ENCLAVE_FILE, - debug, - &mut launch_token, - &mut launch_token_updated, - &mut misc_attr, - ) + + #[cfg(sgx_sim)] + { + println!( + "[*] Running unit tests in SGX simulation mode with prohibited instruction handling" + ); + + // Install SIGILL handler before creating enclave + if let Err(e) = install_sigill_handler() { + panic!("Failed to install SIGILL handler: {e}"); + } + + // Use the patched enclave creation function + sgx_urts::simulate::create_patched_enclave(ENCLAVE_FILE, debug.into(), &mut misc_attr) + } + + #[cfg(not(sgx_sim))] + { + println!("[*] Running unit tests in SGX hardware mode"); + let mut launch_token: sgx_launch_token_t = [0; 1024]; + let mut launch_token_updated: i32 = 0; + SgxEnclave::create( + ENCLAVE_FILE, + debug.into(), + &mut launch_token, + &mut launch_token_updated, + &mut misc_attr, + ) + } } fn main() { + // Initialize tracing + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::from_default_env() + .add_directive("sgx_urts=debug".parse().unwrap()), + ) + .init(); + let enclave = match init_enclave() { Ok(r) => { println!("[+] Init Enclave Successful {}!", r.geteid()); @@ -85,5 +188,36 @@ fn main() { println!("[+] unit_test ended with {retval} tests passed!"); + // Verify trap counts if in simulation mode + #[cfg(sgx_sim)] + { + println!("\n[*] Verifying trap counts..."); + let trap_count = PROHIBITED_INSTRUCTION_COUNT.load(Ordering::SeqCst); + + // We expect exactly 4 traps: one each for CPUID, SYSCALL, SYSENTER, and INT 0x80 + const EXPECTED_TRAP_COUNT: u32 = 4; + + println!("[*] Total prohibited instruction traps: {trap_count}"); + println!("[*] Expected trap count: {EXPECTED_TRAP_COUNT}"); + + // Check for exact match + if trap_count == EXPECTED_TRAP_COUNT { + println!("[+] Prohibited instructions were successfully trapped (exact match)!"); + } else if trap_count == 0 { + println!("[-] No prohibited instructions were trapped!"); + enclave.destroy(); + eprintln!("\n[-] CRITICAL: Prohibited instruction trapping failed! Aborting."); + std::process::exit(1); + } else { + println!("[-] Trap count mismatch!"); + println!(" Expected: {EXPECTED_TRAP_COUNT}, but got: {trap_count}"); + enclave.destroy(); + eprintln!( + "\n[-] CRITICAL: Incorrect number of prohibited instruction traps! Aborting." + ); + std::process::exit(1); + } + } + enclave.destroy(); } diff --git a/unit-test/enclave/build.rs b/unit-test/enclave/build.rs index a938ba0..2d2c612 100644 --- a/unit-test/enclave/build.rs +++ b/unit-test/enclave/build.rs @@ -1,4 +1,15 @@ fn main() { + // Check SGX_MODE environment variable at compile time + let sgx_mode = std::env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string()); + + // Set configuration based on SGX_MODE + println!("cargo:rustc-check-cfg=cfg(sgx_sim)"); + if sgx_mode == "SW" { + println!("cargo:rustc-cfg=sgx_sim"); + } + + println!("cargo:rerun-if-env-changed=SGX_MODE"); + sgx_build::SgxBuilder::new() .build_enclave("Enclave.edl") .expect("Failed to build SGX enclave"); diff --git a/unit-test/enclave/src/lib.rs b/unit-test/enclave/src/lib.rs index 5f78445..5cc58e9 100644 --- a/unit-test/enclave/src/lib.rs +++ b/unit-test/enclave/src/lib.rs @@ -77,6 +77,9 @@ use test_types::*; mod test_crate; use test_crate::*; +mod test_signal; +use test_signal::*; + // Simple test result type type TestResult = Result<(), &'static str>; @@ -282,6 +285,60 @@ pub extern "C" fn test_main_entrance() -> size_t { } } + // Signal handler tests (only in simulation mode) + #[cfg(sgx_sim)] + { + println!("\nRunning signal handler tests (simulation mode only):"); + + print!("test_cpuid_trap ... "); + match test_cpuid_trap() { + Ok(_) => { + println!("ok"); + passed += 1; + } + Err(e) => { + println!("failed: {:?}", e); + failed += 1; + } + } + + print!("test_syscall_trap ... "); + match test_syscall_trap() { + Ok(_) => { + println!("ok"); + passed += 1; + } + Err(e) => { + println!("failed: {:?}", e); + failed += 1; + } + } + + print!("test_sysenter_trap ... "); + match test_sysenter_trap() { + Ok(_) => { + println!("ok"); + passed += 1; + } + Err(e) => { + println!("failed: {:?}", e); + failed += 1; + } + } + + print!("test_int80_trap ... "); + match test_int80_trap() { + Ok(_) => { + println!("ok"); + passed += 1; + } + Err(e) => { + println!("failed: {:?}", e); + failed += 1; + } + } + } + println!("\ntest result: ok. {} passed; {} failed", passed, failed); passed diff --git a/unit-test/enclave/src/test_signal.rs b/unit-test/enclave/src/test_signal.rs new file mode 100644 index 0000000..84d5ceb --- /dev/null +++ b/unit-test/enclave/src/test_signal.rs @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use crate::{print, TestResult}; + +// Test CPUID instruction execution and trapping +pub fn test_cpuid_trap() -> TestResult { + // This test only works in simulation mode where instructions are patched + #[cfg(not(sgx_sim))] + { + // In HW mode, CPUID would cause an exception, so we skip this test + return Ok(()); + } + + #[cfg(sgx_sim)] + { + // In simulation mode, CPUID should be patched to UD2 and handled by our trap handler + unsafe { + use core::arch::x86_64::__cpuid; + __cpuid(0) + }; + // If we reach here, the trap handler successfully handled the UD2 + println!("CPUID trap test: Successfully trapped and continued execution"); + Ok(()) + } +} + +// Test SYSCALL instruction execution and trapping +pub fn test_syscall_trap() -> TestResult { + #[cfg(not(sgx_sim))] + { + return Ok(()); + } + + #[cfg(sgx_sim)] + { + // Execute SYSCALL instruction + unsafe { + core::arch::asm!("syscall", options(nostack, preserves_flags)); + } + // If we reach here, the trap handler successfully handled the UD2 + println!("SYSCALL trap test: Successfully trapped and continued execution"); + + Ok(()) + } +} + +// Test SYSENTER instruction execution and trapping +pub fn test_sysenter_trap() -> TestResult { + #[cfg(not(sgx_sim))] + { + return Ok(()); + } + + #[cfg(sgx_sim)] + { + // Execute SYSENTER instruction + unsafe { + core::arch::asm!("sysenter", options(nostack, preserves_flags)); + } + // If we reach here, the trap handler successfully handled the UD2 + println!("SYSENTER trap test: Successfully trapped and continued execution"); + Ok(()) + } +} + +// Test INT 0x80 instruction execution and trapping +pub fn test_int80_trap() -> TestResult { + #[cfg(not(sgx_sim))] + { + return Ok(()); + } + + #[cfg(sgx_sim)] + { + // Execute INT 0x80 instruction + unsafe { + core::arch::asm!("int 0x80", options(nostack, preserves_flags)); + } + // If we reach here, the trap handler successfully handled the UD2 + println!("INT 0x80 trap test: Successfully trapped and continued execution"); + Ok(()) + } +}