From 6e217c3af0b39a6c81d6449d4934b604e3c6d873 Mon Sep 17 00:00:00 2001 From: swananan Date: Sun, 24 May 2026 09:22:26 +0800 Subject: [PATCH] ci: require safety comments for unsafe blocks Enable clippy::undocumented_unsafe_blocks in CI and document the existing unsafe blocks and impls so the workspace passes the stricter lint. --- .github/workflows/ci.yml | 2 +- e2e-tests/tests/common/mod.rs | 8 ++++-- e2e-tests/tests/common/termination.rs | 1 + .../fixtures/rust_global_program/src/main.rs | 2 ++ e2e-tests/tests/script_execution.rs | 2 ++ .../src/ebpf/codegen/backtrace.rs | 4 +++ .../src/ebpf/codegen/expr_error.rs | 7 +++++ .../src/ebpf/codegen/format.rs | 26 +++++++++++++++++++ .../ebpf/codegen/print_complex_variable.rs | 25 ++++++++++++++++++ .../src/ebpf/codegen/print_string_index.rs | 6 +++++ .../src/ebpf/codegen/print_variable_index.rs | 11 ++++++++ ghostscope-compiler/src/ebpf/context.rs | 2 ++ ghostscope-compiler/src/ebpf/expression.rs | 21 +++++++++++++++ .../src/ebpf/helper_functions.rs | 20 ++++++++++++++ ghostscope-compiler/src/ebpf/instruction.rs | 14 ++++++++++ ghostscope-dwarf/src/binary/mapped_file.rs | 6 +++++ ghostscope-process/ebpf/sysmon-bpf/src/lib.rs | 19 +++++++++++++- ghostscope-process/src/module_probe.rs | 3 +++ ghostscope-process/src/pinned_bpf_maps.rs | 3 +++ ghostscope-process/src/sysmon.rs | 4 +++ .../output/streaming_parser_bridge.rs | 2 ++ ghostscope/src/util.rs | 1 + 22 files changed, 185 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29d672fd..de38781b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: ${{ runner.os }}-${{ github.job }}-target- - name: Run clippy - run: cargo clippy --all-targets --all-features -- -D warnings + run: cargo clippy --all-targets --all-features -- -D warnings -D clippy::undocumented_unsafe_blocks test: name: Test Suite diff --git a/e2e-tests/tests/common/mod.rs b/e2e-tests/tests/common/mod.rs index 1051af04..f628702a 100644 --- a/e2e-tests/tests/common/mod.rs +++ b/e2e-tests/tests/common/mod.rs @@ -266,7 +266,7 @@ pub fn init() { }); // Register an atexit cleanup to remove built fixtures after all tests - REGISTER_CLEANUP.call_once(|| unsafe { + REGISTER_CLEANUP.call_once(|| { extern "C" fn cleanup_fixtures() { if preserve_precompiled_fixtures() { return; @@ -277,7 +277,11 @@ pub fn init() { fixture.cleanup(&base); } } - libc::atexit(cleanup_fixtures); + // SAFETY: cleanup_fixtures has C ABI, captures no Rust references, and + // remains available for the process lifetime. + unsafe { + libc::atexit(cleanup_fixtures); + } }); } diff --git a/e2e-tests/tests/common/termination.rs b/e2e-tests/tests/common/termination.rs index 9688f4b1..9dd69f51 100644 --- a/e2e-tests/tests/common/termination.rs +++ b/e2e-tests/tests/common/termination.rs @@ -6,6 +6,7 @@ const TERMINATION_POLL_INTERVAL: Duration = Duration::from_millis(50); #[cfg(unix)] pub(crate) fn send_sigterm(pid: u32, label: &str) -> anyhow::Result<()> { + // SAFETY: libc::kill has no pointer arguments; pid and signal are plain values. let rc = unsafe { libc::kill(pid as libc::pid_t, libc::SIGTERM) }; if rc == 0 { return Ok(()); diff --git a/e2e-tests/tests/fixtures/rust_global_program/src/main.rs b/e2e-tests/tests/fixtures/rust_global_program/src/main.rs index 9bf96d9f..2e64c0b7 100644 --- a/e2e-tests/tests/fixtures/rust_global_program/src/main.rs +++ b/e2e-tests/tests/fixtures/rust_global_program/src/main.rs @@ -114,6 +114,8 @@ pub mod math { } fn touch_globals() -> i32 { + // SAFETY: This single-threaded fixture mutates its own static test globals to + // keep DWARF global-variable scenarios observable. unsafe { G_COUNTER = G_COUNTER.wrapping_add(1); CONFIG.a = CONFIG.a.wrapping_add(1); diff --git a/e2e-tests/tests/script_execution.rs b/e2e-tests/tests/script_execution.rs index fbc3cc30..b0b34b30 100644 --- a/e2e-tests/tests/script_execution.rs +++ b/e2e-tests/tests/script_execution.rs @@ -396,6 +396,8 @@ fn ensure_global_cleanup_registered() { println!("🧹 Global cleanup completed"); } + // SAFETY: cleanup_on_exit has C ABI, captures no Rust references, and + // remains available for the process lifetime. unsafe { libc::atexit(cleanup_on_exit); } diff --git a/ghostscope-compiler/src/ebpf/codegen/backtrace.rs b/ghostscope-compiler/src/ebpf/codegen/backtrace.rs index 09a22073..7690c92d 100644 --- a/ghostscope-compiler/src/ebpf/codegen/backtrace.rs +++ b/ghostscope-compiler/src/ebpf/codegen/backtrace.rs @@ -17,6 +17,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .into_value_after_runtime_returns(); // Write InstructionHeader.inst_type + // SAFETY: inst_buffer points at a reserved instruction region and the + // offset is within InstructionHeader. let inst_type_ptr = unsafe { self.builder .build_gep( @@ -39,6 +41,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?; // Write InstructionHeader.data_length (u16) + // SAFETY: inst_buffer points at a reserved instruction region and the + // data_length offset is within InstructionHeader. let data_length_ptr = unsafe { self.builder .build_gep( diff --git a/ghostscope-compiler/src/ebpf/codegen/expr_error.rs b/ghostscope-compiler/src/ebpf/codegen/expr_error.rs index bce1d7be..257528b3 100644 --- a/ghostscope-compiler/src/ebpf/codegen/expr_error.rs +++ b/ghostscope-compiler/src/ebpf/codegen/expr_error.rs @@ -28,6 +28,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?; // data_length + // SAFETY: inst_buffer points at a reserved ExprError instruction region + // and data_length is within InstructionHeader. let data_length_ptr = unsafe { self.builder .build_gep( @@ -61,6 +63,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // Payload fields after header // string_index at offset sizeof(InstructionHeader) + 0 (u16) + // SAFETY: the payload immediately follows InstructionHeader in the + // reserved ExprError instruction region. let si_ptr = unsafe { self.builder .build_gep( @@ -95,6 +99,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store string_index: {e}")))?; // error_code at +2, flags at +3 + // SAFETY: error_code offset is within ExprErrorData in the reserved payload. let ec_ptr = unsafe { self.builder .build_gep( @@ -125,6 +130,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { self.builder .build_store(ec_ptr, ec_i8) .map_err(|e| CodeGenError::LLVMError(format!("Failed to store error_code: {e}")))?; + // SAFETY: flags offset is within ExprErrorData in the reserved payload. let fl_ptr = unsafe { self.builder .build_gep( @@ -155,6 +161,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store flags: {e}")))?; // failing_addr at +4 (u64) + // SAFETY: failing_addr offset is within ExprErrorData in the reserved payload. let addr_ptr = unsafe { self.builder .build_gep( diff --git a/ghostscope-compiler/src/ebpf/codegen/format.rs b/ghostscope-compiler/src/ebpf/codegen/format.rs index 7e2e0e1e..646f7927 100644 --- a/ghostscope-compiler/src/ebpf/codegen/format.rs +++ b/ghostscope-compiler/src/ebpf/codegen/format.rs @@ -475,6 +475,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .build_store(buffer, inst_type_val) .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?; // data_length at +1 + // SAFETY: buffer points at a reserved PrintComplexFormat instruction + // region and offset 1 is the InstructionHeader data_length field. let data_length_ptr = unsafe { self.builder .build_gep( @@ -504,6 +506,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?; // Write PrintComplexFormatData at offset 4 + // SAFETY: PrintComplexFormatData starts immediately after InstructionHeader + // at offset 4 in the reserved instruction region. let data_ptr = unsafe { self.builder .build_gep( @@ -534,6 +538,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .build_store(fsi_ptr, fsi_val) .map_err(|e| CodeGenError::LLVMError(format!("Failed to store fsi: {e}")))?; // arg_count (u8) at +2 + // SAFETY: arg_count offset is within PrintComplexFormatData. let arg_cnt_ptr = unsafe { self.builder .build_gep( @@ -558,6 +563,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let reserved_len = effective_reserved[arg_index]; // Base pointer = data_ptr + offset + // SAFETY: offset is advanced by the statically computed per-argument + // payload sizes and remains within the reserved instruction region. let arg_base = unsafe { self.builder .build_gep( @@ -590,6 +597,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store vni: {e}")))?; // type_index(u16) at +2 + // SAFETY: type_index offset is within the per-argument payload header. let ti_ptr = unsafe { self.builder .build_gep( @@ -618,6 +626,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store ti: {e}")))?; // status(u8) at +5 + // SAFETY: status offset is within the per-argument payload header. let apl_ptr = unsafe { self.builder .build_gep( @@ -635,6 +644,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store status: {e}")))?; // access_path_len(u8) at +4 + // SAFETY: access_path_len offset is within the per-argument payload header. let apl_ptr2 = unsafe { self.builder .build_gep( @@ -656,6 +666,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // access_path bytes at +6..+6+len for (i, b) in a.access_path.iter().enumerate() { + // SAFETY: i is bounded by access_path.len(), which was included in + // the per-argument reserved payload length. let byte_ptr = unsafe { self.builder .build_gep( @@ -676,6 +688,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { } // data_len(u16) at +6+path_len (store reserved_len to keep layout consistent) + // SAFETY: data_len follows the access path bytes inside the reserved + // per-argument payload. let dl_ptr = unsafe { self.builder .build_gep( @@ -707,6 +721,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_len: {e}")))?; // variable data starts at +8+path_len + // SAFETY: var_data_ptr follows the per-argument header and access path + // inside the reserved payload. let var_data_ptr = unsafe { self.builder .build_gep( @@ -728,6 +744,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { match &a.source { ComplexArgSource::ImmediateBytes { bytes, .. } => { for (i, b) in bytes.iter().enumerate() { + // SAFETY: i is bounded by bytes.len(), and immediate bytes + // are included in the per-argument reserved payload. let byte_ptr = unsafe { self.builder .build_gep( @@ -874,6 +892,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { self.builder .build_store(errno_ptr, errno) .map_err(|e| CodeGenError::LLVMError(e.to_string()))?; + // SAFETY: read-error payload reserves 12 bytes, so addr starts + // at byte 4 after the errno field. let addr_ptr_i8 = unsafe { self.builder .build_gep( @@ -1099,6 +1119,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(e.to_string()))?; } if eff_max_len as usize >= DYNAMIC_READ_ERROR_PAYLOAD_LEN { + // SAFETY: eff_max_len is at least the 12-byte read-error + // payload, so addr starts at byte 4 after errno. let addr_ptr_i8 = unsafe { self.builder .build_gep( @@ -1281,6 +1303,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { &format!("expr_byte_{i}"), ) .map_err(|e| CodeGenError::LLVMError(e.to_string()))?; + // SAFETY: i is bounded by n, the immediate payload + // size reserved for this argument. let byte_ptr = unsafe { self.builder .build_gep( @@ -1423,6 +1447,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { CodeGenError::LLVMError(format!("Failed to store errno: {e}")) })?; // write addr at [4..12] + // SAFETY: read-error payload reserves 12 bytes, so addr starts + // at byte 4 after the errno field. let addr_ptr_i8 = unsafe { self.builder .build_gep( diff --git a/ghostscope-compiler/src/ebpf/codegen/print_complex_variable.rs b/ghostscope-compiler/src/ebpf/codegen/print_complex_variable.rs index d967cb88..a3faa8a1 100644 --- a/ghostscope-compiler/src/ebpf/codegen/print_complex_variable.rs +++ b/ghostscope-compiler/src/ebpf/codegen/print_complex_variable.rs @@ -30,6 +30,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?; // Write data_length (u16) at offset 1 + // SAFETY: inst_buffer points at a reserved PrintComplexVariable instruction + // region and offset 1 is the InstructionHeader data_length field. let data_length_ptr = unsafe { self.builder .build_gep( @@ -60,6 +62,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?; // Data pointer (after header) + // SAFETY: data_ptr starts immediately after InstructionHeader in the + // reserved instruction region. let data_ptr = unsafe { self.builder .build_gep( @@ -78,6 +82,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .const_int(var_name_index as u64, false); let var_name_index_off = std::mem::offset_of!(PrintComplexVariableData, var_name_index) as u64; + // SAFETY: var_name_index_off is generated from PrintComplexVariableData. let var_name_index_ptr_i8 = unsafe { self.builder .build_gep( @@ -106,6 +111,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // type_index (u16) let type_index_offset = std::mem::offset_of!(PrintComplexVariableData, type_index) as u64; + // SAFETY: type_index_offset is generated from PrintComplexVariableData. let type_index_ptr_i8 = unsafe { self.builder .build_gep( @@ -134,6 +140,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // access_path_len (u8) = 0 let access_path_len_off = std::mem::offset_of!(PrintComplexVariableData, access_path_len) as u64; + // SAFETY: access_path_len_off is generated from PrintComplexVariableData. let access_path_len_ptr = unsafe { self.builder .build_gep( @@ -157,6 +164,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // status (u8) = 0 let status_off = std::mem::offset_of!(PrintComplexVariableData, status) as u64; + // SAFETY: status_off is generated from PrintComplexVariableData. let status_ptr = unsafe { self.builder .build_gep( @@ -173,6 +181,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // data_len (u16) let data_len_off = std::mem::offset_of!(PrintComplexVariableData, data_len) as u64; + // SAFETY: data_len_off is generated from PrintComplexVariableData. let data_len_ptr = unsafe { self.builder .build_gep( @@ -199,6 +208,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_len: {e}")))?; // variable data starts right after PrintComplexVariableData (no access path) + // SAFETY: total_size reserved byte_len bytes after PrintComplexVariableData. let var_data_ptr = unsafe { self.builder .build_gep( @@ -321,6 +331,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { &format!("expr_byte_{i}"), ) .map_err(|e| CodeGenError::LLVMError(e.to_string()))?; + // SAFETY: i is bounded by byte_len and var_data_ptr covers byte_len bytes. let byte_ptr = unsafe { self.builder .build_gep( @@ -406,6 +417,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // Write InstructionHeader // data_length field (u16) at offset 1 + // SAFETY: inst_buffer points at a reserved PrintComplexVariable instruction + // region and offset 1 is the InstructionHeader data_length field. let data_length_ptr = unsafe { self.builder .build_gep( @@ -440,6 +453,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { ); // Data pointer (after header) + // SAFETY: data_ptr starts immediately after InstructionHeader in the + // reserved instruction region. let data_ptr = unsafe { self.builder .build_gep( @@ -459,6 +474,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // Store var_name_index at offset offsetof(PrintComplexVariableData, var_name_index) let var_name_index_off = std::mem::offset_of!(PrintComplexVariableData, var_name_index) as u64; + // SAFETY: var_name_index_off is generated from PrintComplexVariableData. let var_name_index_ptr_i8 = unsafe { self.builder .build_gep( @@ -492,6 +508,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // type_index (u16) right after var_name_index // type_index at offset offsetof(PrintComplexVariableData, type_index) = 2 let type_index_offset = std::mem::offset_of!(PrintComplexVariableData, type_index) as u64; + // SAFETY: type_index_offset is generated from PrintComplexVariableData. let type_index_ptr_i8 = unsafe { self.builder .build_gep( @@ -528,6 +545,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // access_path_len at offset offsetof(..., access_path_len) let access_path_len_off = std::mem::offset_of!(PrintComplexVariableData, access_path_len) as u64; + // SAFETY: access_path_len_off is generated from PrintComplexVariableData. let access_path_len_ptr = unsafe { self.builder .build_gep( @@ -560,6 +578,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // status (u8) at offset offsetof(..., status) let status_off = std::mem::offset_of!(PrintComplexVariableData, status) as u64; + // SAFETY: status_off is generated from PrintComplexVariableData. let status_ptr = unsafe { self.builder .build_gep( @@ -583,6 +602,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // data_len (u16) let data_len_off = std::mem::offset_of!(PrintComplexVariableData, data_len) as u64; + // SAFETY: data_len_off is generated from PrintComplexVariableData. let data_len_ptr = unsafe { self.builder .build_gep( @@ -615,6 +635,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // Optimized-out case is handled earlier by resolving to an OptimizedOut type and ImmediateBytes path. // access_path bytes start after PrintComplexVariableData + // SAFETY: total_size reserved access_path_len bytes after PrintComplexVariableData. let access_path_ptr = unsafe { self.builder .build_gep( @@ -633,6 +654,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // Copy access path bytes for (i, &byte) in access_path_bytes.iter().enumerate().take(access_path_len) { + // SAFETY: i is bounded by access_path_len and access_path_ptr covers that region. let byte_ptr = unsafe { self.builder .build_gep( @@ -655,6 +677,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { } // Variable data starts after access_path + // SAFETY: variable_data_ptr starts after the reserved access_path bytes and + // total_size reserved the payload region. let variable_data_ptr = unsafe { self.builder .build_gep( @@ -813,6 +837,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .build_store(errno_ptr, errno) .map_err(|e| CodeGenError::LLVMError(format!("Failed to store errno: {e}")))?; // write addr at [4..12] + // SAFETY: the error payload reserves 12 bytes, so addr starts at byte 4. let addr_ptr_i8 = unsafe { self.builder .build_gep( diff --git a/ghostscope-compiler/src/ebpf/codegen/print_string_index.rs b/ghostscope-compiler/src/ebpf/codegen/print_string_index.rs index 1c737c42..0dc1b3ee 100644 --- a/ghostscope-compiler/src/ebpf/codegen/print_string_index.rs +++ b/ghostscope-compiler/src/ebpf/codegen/print_string_index.rs @@ -28,6 +28,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // Fill instruction header using byte offsets // inst_type at offset 0 (first field of InstructionHeader) + // SAFETY: inst_buffer points at a reserved PrintStringIndex instruction + // region and inst_type is within InstructionHeader. let inst_type_ptr = unsafe { self.builder .build_gep( @@ -49,6 +51,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .build_store(inst_type_ptr, inst_type_val) .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?; + // SAFETY: inst_buffer points at a reserved PrintStringIndex instruction + // region and data_length is within InstructionHeader. let data_length_ptr = unsafe { self.builder .build_gep( @@ -81,6 +85,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?; // Fill string index data (after InstructionHeader) + // SAFETY: the string index payload starts immediately after InstructionHeader + // in the reserved PrintStringIndex instruction region. let string_index_ptr = unsafe { self.builder .build_gep( diff --git a/ghostscope-compiler/src/ebpf/codegen/print_variable_index.rs b/ghostscope-compiler/src/ebpf/codegen/print_variable_index.rs index cce1bdb4..f4a1d0bd 100644 --- a/ghostscope-compiler/src/ebpf/codegen/print_variable_index.rs +++ b/ghostscope-compiler/src/ebpf/codegen/print_variable_index.rs @@ -71,6 +71,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?; // Store data_length field of InstructionHeader + // SAFETY: inst_buffer points at a reserved PrintVariableIndex instruction + // region and data_length is within InstructionHeader. let data_length_ptr = unsafe { self.builder .build_gep( @@ -104,6 +106,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_length: {e}")))?; // Write PrintVariableIndexData after InstructionHeader + // SAFETY: variable_data_start is exactly after InstructionHeader in the + // reserved instruction region. let variable_data_start = unsafe { self.builder .build_gep( @@ -121,6 +125,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { }; // Store var_name_index using correct offset + // SAFETY: var_name_index offset is within PrintVariableIndexData. let var_name_index_ptr = unsafe { self.builder .build_gep( @@ -155,6 +160,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store var_name_index: {e}")))?; // Store type_encoding using correct offset + // SAFETY: type_encoding offset is within PrintVariableIndexData. let type_encoding_ptr = unsafe { self.builder .build_gep( @@ -179,6 +185,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store type_encoding: {e}")))?; // Store data_len using correct offset + // SAFETY: data_len offset is within PrintVariableIndexData. let data_len_ptr = unsafe { self.builder .build_gep( @@ -206,6 +213,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store data_len: {e}")))?; // Store type_index using correct offset + // SAFETY: type_index offset is within PrintVariableIndexData. let type_index_ptr = unsafe { self.builder .build_gep( @@ -235,6 +243,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store type_index: {e}")))?; // Store status (set to 0) + // SAFETY: status offset is within PrintVariableIndexData. let status_ptr = unsafe { self.builder .build_gep( @@ -259,6 +268,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let var_data = self.resolve_variable_value(var_name, type_encoding, Some(status_ptr))?; // Store actual variable data after PrintVariableIndexData structure + // SAFETY: var_data_ptr starts after PrintVariableIndexData inside the + // reserved instruction region, which included data_size bytes. let var_data_ptr = unsafe { self.builder .build_gep( diff --git a/ghostscope-compiler/src/ebpf/context.rs b/ghostscope-compiler/src/ebpf/context.rs index 52947bf8..e429dd67 100644 --- a/ghostscope-compiler/src/ebpf/context.rs +++ b/ghostscope-compiler/src/ebpf/context.rs @@ -806,6 +806,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::Builder(e.to_string()))?; self.builder.position_at_end(helper_ok_block); + // SAFETY: pidns_info_alloca has the pid namespace helper result layout + // [pid, tgid], so [0, 1] addresses the tgid field. let tgid_ptr = unsafe { self.builder.build_gep( pidns_info_ty, diff --git a/ghostscope-compiler/src/ebpf/expression.rs b/ghostscope-compiler/src/ebpf/expression.rs index d1d526d7..113c4a5e 100644 --- a/ghostscope-compiler/src/ebpf/expression.rs +++ b/ghostscope-compiler/src/ebpf/expression.rs @@ -149,6 +149,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { ) .map_err(|e| CodeGenError::Builder(e.to_string()))?; + // SAFETY: key_alloca temporarily holds the two-field pid namespace helper + // result, so [0, 0] addresses the pid field. let ns_pid_ptr = unsafe { self.builder.build_gep( key_arr_ty, @@ -158,6 +160,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { ) } .map_err(|e| CodeGenError::LLVMError(e.to_string()))?; + // SAFETY: key_alloca temporarily holds the two-field pid namespace helper + // result, so [0, 1] addresses the tgid field. let ns_tgid_ptr = unsafe { self.builder.build_gep( key_arr_ty, @@ -1025,6 +1029,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let idx0 = i32_ty.const_zero(); for i in 0..(cap as usize) { let idx_i = i32_ty.const_int(i as u64, false); + // SAFETY: buf_a is a cap-sized array and i is bounded by cap. let pa = unsafe { self.builder .build_gep(arr_a_ty, buf_a, &[idx0, idx_i], &format!("hex_a_i{i}")) @@ -1103,6 +1108,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let idx0 = i32_ty.const_zero(); for i in 0..(cap as usize) { let idx_i = i32_ty.const_int(i as u64, false); + // SAFETY: buf_b is a cap-sized array and i is bounded by cap. let pb = unsafe { self.builder .build_gep(arr_b_ty, buf_b, &[idx0, idx_i], &format!("hex_b_i{i}")) @@ -1328,6 +1334,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { ) .map_err(|e| CodeGenError::Builder(e.to_string()))?; // a[i] + // SAFETY: i is bounded by cmp_bound, which is clamped to the buffer cap. let pa = unsafe { self.builder .build_gep(arr_a_ty, buf_a, &[idx0, idx_i], &format!("memcmp_a_i{i}")) @@ -1342,6 +1349,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { _ => return Err(CodeGenError::LLVMError("memcmp load a != i8".into())), }; // b[i] + // SAFETY: i is bounded by cmp_bound, which is clamped to the buffer cap. let pb = unsafe { self.builder .build_gep(arr_b_ty, buf_b, &[idx0, idx_i], &format!("memcmp_b_i{i}")) @@ -1732,6 +1740,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let mut acc = self.context.i8_type().const_zero(); for (i, b) in lit.as_bytes().iter().take(cmp_bound as usize).enumerate() { let idx_i = i32_ty.const_int(i as u64, false); + // SAFETY: i is bounded by cmp_bound, which is clamped to the buffer cap. let ptr_i = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr") @@ -3952,6 +3961,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let i32_ty = self.context.i32_type(); let idx0 = i32_ty.const_zero(); let idx_l = i32_ty.const_int(lit_len as u64, false); + // SAFETY: the string read requested lit_len + 1 bytes, so index + // lit_len is within the scratch buffer. let char_ptr = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr") @@ -3979,6 +3990,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let mut acc = self.context.i8_type().const_zero(); for (i, b) in lit_bytes.iter().enumerate() { let idx_i = i32_ty.const_int(i as u64, false); + // SAFETY: lit_bytes length is bounded by the scratch buffer size. let ptr_i = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr") @@ -4070,6 +4082,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let i32_ty = self.context.i32_type(); let idx0 = i32_ty.const_zero(); let idx_l = i32_ty.const_int(lit_len as u64, false); + // SAFETY: the string read requested lit_len + 1 bytes, so index + // lit_len is within the scratch buffer. let char_ptr = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr") @@ -4096,6 +4110,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let mut acc = self.context.i8_type().const_zero(); for (i, b) in lit_bytes.iter().enumerate() { let idx_i = i32_ty.const_int(i as u64, false); + // SAFETY: lit_bytes length is bounded by the scratch buffer size. let ptr_i = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr") @@ -4184,6 +4199,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let i32_ty = self.context.i32_type(); let idx0 = i32_ty.const_zero(); let idx_l = i32_ty.const_int(lit_len as u64, false); + // SAFETY: the string read requested lit_len + 1 bytes, so + // index lit_len is within the scratch buffer. let char_ptr = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr") @@ -4214,6 +4231,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let mut acc = self.context.i8_type().const_zero(); for (i, b) in lit_bytes.iter().enumerate() { let idx_i = i32_ty.const_int(i as u64, false); + // SAFETY: lit_bytes length is bounded by the scratch buffer size. let ptr_i = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr") @@ -4279,6 +4297,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let i32_ty = self.context.i32_type(); let idx0 = i32_ty.const_zero(); let idx_l = i32_ty.const_int(lit_len as u64, false); + // SAFETY: the string read requested lit_len + 1 bytes, so + // index lit_len is within the scratch buffer. let char_ptr = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_l], "nul_ptr") @@ -4308,6 +4328,7 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let mut acc = self.context.i8_type().const_zero(); for (i, b) in lit_bytes.iter().enumerate() { let idx_i = i32_ty.const_int(i as u64, false); + // SAFETY: lit_bytes length is bounded by the scratch buffer size. let ptr_i = unsafe { self.builder .build_gep(arr_ty, buf_global, &[idx0, idx_i], "ch_ptr") diff --git a/ghostscope-compiler/src/ebpf/helper_functions.rs b/ghostscope-compiler/src/ebpf/helper_functions.rs index 5608ff04..b9abf6b5 100644 --- a/ghostscope-compiler/src/ebpf/helper_functions.rs +++ b/ghostscope-compiler/src/ebpf/helper_functions.rs @@ -71,6 +71,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let i32_type = self.context.i32_type(); let key_arr_ty = i32_type.array_type(4); let zero = i32_type.const_zero(); + // SAFETY: pm_key_alloca is a [4 x i32] entry-block alloca and + // [0, 0] addresses its first byte-compatible element. return unsafe { self.builder .build_gep( @@ -121,6 +123,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { })?; let key_arr_ty = i32_type.array_type(4); let zero = i32_type.const_zero(); + // SAFETY: key_alloca is the [4 x i32] pm_key stack slot and [0, 0] + // addresses the pid key element. let key_ptr = unsafe { self.builder .build_gep( @@ -452,6 +456,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { })?; // Get i32* to the first element (&key[0]) let zero = i32_type.const_zero(); + // SAFETY: key_alloca is the [4 x i32] pm_key stack slot and [0, 0] + // addresses the first element. let base_i32_ptr = unsafe { self.builder .build_gep(key_arr_ty, key_alloca, &[zero, zero], "pm_key_i32_ptr") @@ -534,6 +540,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { "offset_ns_helper_ok", ) .map_err(|e| CodeGenError::Builder(e.to_string()))?; + // SAFETY: key_alloca temporarily holds the two-field pid namespace + // helper result, so [0, 1] addresses the tgid field. let ns_tgid_ptr = unsafe { self.builder.build_gep( key_arr_ty, @@ -568,6 +576,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // Zero padding at key[1] for deterministic key bytes let idx1 = i32_type.const_int(1, false); + // SAFETY: base_i32_ptr points to key[0] of a four-element i32 array; index + // 1 is the padding field. let pad_ptr = unsafe { self.builder .build_gep(self.context.i32_type(), base_i32_ptr, &[idx1], "pad_ptr") @@ -583,6 +593,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let idx2 = i32_type.const_int(2, false); let idx3 = i32_type.const_int(3, false); // key[1] left as padding = 0 by default + // SAFETY: base_i32_ptr points to key[0] of a four-element i32 array; index + // 2 is the low cookie word. let cookie_lo_ptr = unsafe { self.builder .build_gep( @@ -593,6 +605,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { ) .map_err(|e| CodeGenError::LLVMError(e.to_string()))? }; + // SAFETY: base_i32_ptr points to key[0] of a four-element i32 array; index + // 3 is the high cookie word. let cookie_hi_ptr = unsafe { self.builder .build_gep( @@ -673,6 +687,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { -> Result> { // GEP in i64 element space let idx_i32 = ctx.context.i32_type().const_int(idx, false); + // SAFETY: val_u64_ptr points at ProcModuleOffsetsValue, represented as + // four contiguous u64 fields; idx is one of 0..=3. let field_ptr = unsafe { ctx.builder .build_gep(ctx.context.i64_type(), base, &[idx_i32], "field_ptr_i64") @@ -792,6 +808,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let i64_type = self.context.i64_type(); let offset_value = i64_type.const_int(pt_regs_offset as u64, false); + // SAFETY: pt_regs_offset was converted to a u64 slot index by the platform + // register mapping, so the generated access targets a pt_regs register slot. let reg_ptr = unsafe { self.builder .build_gep( @@ -1567,6 +1585,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { CodeGenError::LLVMError("pm_key not allocated in entry block".to_string()) })?; let zero = i32_ty.const_zero(); + // SAFETY: key_alloca is the [4 x i32] pm_key stack slot and [0, 0] + // addresses the first key element. let base_i32_ptr = unsafe { self.builder .build_gep(key_arr_ty, key_alloca, &[zero, zero], "percpu_key_i32_ptr") diff --git a/ghostscope-compiler/src/ebpf/instruction.rs b/ghostscope-compiler/src/ebpf/instruction.rs index 3d051b7b..a3f6a547 100644 --- a/ghostscope-compiler/src/ebpf/instruction.rs +++ b/ghostscope-compiler/src/ebpf/instruction.rs @@ -170,6 +170,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .builder .build_int_z_extend(offset_val, i64_ty, "off64") .map_err(|e| CodeGenError::LLVMError(format!("Failed to extend offset: {e}")))?; + // SAFETY: the reserve bounds check proved offset_val..offset_val+size fits + // inside the accumulation buffer. let dest_i8 = unsafe { self.builder .build_gep(self.context.i8_type(), accum_buffer, &[off64], "dest_i8") @@ -363,6 +365,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // timestamp at offset 8 let timestamp = self.get_current_timestamp()?; + // SAFETY: message_buffer points at a reserved trace event header region + // and the timestamp offset is within that header. let timestamp_ptr = unsafe { self.builder .build_gep( @@ -393,6 +397,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { let (event_pid, event_tid) = self.get_host_pid_tid_values()?; // Store pid at offset 16 + // SAFETY: message_buffer points at a reserved trace event header region + // and the pid offset is within that header. let pid_ptr = unsafe { self.builder .build_gep( @@ -419,6 +425,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store pid: {e}")))?; // Store tid at offset 20 + // SAFETY: message_buffer points at a reserved trace event header region + // and the tid offset is within that header. let tid_ptr = unsafe { self.builder .build_gep( @@ -476,6 +484,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { .map_err(|e| CodeGenError::LLVMError(format!("Failed to store inst_type: {e}")))?; // data_length at offset 1 + // SAFETY: end_buffer points at a reserved EndInstruction region and + // data_length is within InstructionHeader. let data_length_ptr = unsafe { self.builder .build_gep( @@ -509,6 +519,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { // Write EndInstructionData at offset 4 // total_instructions + // SAFETY: EndInstructionData starts at END_INSTRUCTION_DATA_OFFSET inside + // the reserved EndInstruction region. let total_instructions_ptr = unsafe { self.builder .build_gep( @@ -545,6 +557,8 @@ impl<'ctx, 'dw> EbpfContext<'ctx, 'dw> { })?; // execution_status at offset 6 + // SAFETY: execution_status offset is within EndInstructionData in the + // reserved EndInstruction region. let status_ptr = unsafe { self.builder .build_gep( diff --git a/ghostscope-dwarf/src/binary/mapped_file.rs b/ghostscope-dwarf/src/binary/mapped_file.rs index 64cbfd43..39977add 100644 --- a/ghostscope-dwarf/src/binary/mapped_file.rs +++ b/ghostscope-dwarf/src/binary/mapped_file.rs @@ -16,6 +16,9 @@ impl MappedFile { pub fn open>(path: P) -> std::io::Result { let path = path.as_ref().to_path_buf(); let file = std::fs::File::open(&path)?; + // SAFETY: The file is opened read-only and this type exposes the mapping + // immutably for object/DWARF parsing; callers must not concurrently modify + // the mapped file while the mapping is alive. let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? }; Ok(Self { data: mmap, path }) } @@ -70,7 +73,10 @@ impl Deref for DwarfBytes { } } +// SAFETY: DwarfBytes dereferences into Arc-backed storage, so the slice address +// remains stable across moves of the DwarfBytes value. unsafe impl gimli::StableDeref for DwarfBytes {} +// SAFETY: Cloning DwarfBytes clones the Arc backing store and preserves stable deref. unsafe impl gimli::CloneStableDeref for DwarfBytes {} pub(crate) type DwarfEndian = gimli::RunTimeEndian; diff --git a/ghostscope-process/ebpf/sysmon-bpf/src/lib.rs b/ghostscope-process/ebpf/sysmon-bpf/src/lib.rs index 0fb31534..a17c20f8 100644 --- a/ghostscope-process/ebpf/sysmon-bpf/src/lib.rs +++ b/ghostscope-process/ebpf/sysmon-bpf/src/lib.rs @@ -36,11 +36,17 @@ static mut TARGET_EXEC_COMM: Array<[u8; 16]> = Array::pinned(1, 0); fn write_event(ctx: &TracePointContext, mut ev: SysEvent) { // Prefer direct helper to avoid large memcpy/memmove codegen let size = core::mem::size_of::() as u64; + // SAFETY: eBPF map statics are accessed only through BPF helper-compatible + // pointers while the verifier controls program concurrency. let map_ptr = unsafe { core::ptr::addr_of_mut!(SYS_EVENTS) } as *mut _; let data_ptr = &mut ev as *mut _ as *mut _; + // SAFETY: map_ptr points to SYS_EVENTS and data_ptr/size describe the local + // SysEvent value for the duration of the helper call. let ret = unsafe { aya_ebpf::helpers::bpf_ringbuf_output(map_ptr, data_ptr, size, 0) }; if ret < 0 { // Fallback to perf event if ringbuf output fails + // SAFETY: SYS_EVENTS_PERF is the statically declared BPF perf map and ev + // remains valid for the helper call. let _ = unsafe { SYS_EVENTS_PERF.output(ctx, &mut ev, 0) }; } } @@ -54,8 +60,14 @@ fn current_tgid() -> u32 { #[inline(always)] fn exec_comm_matches() -> bool { + // SAFETY: TARGET_EXEC_COMM is a single-entry BPF array; get_ptr returns None + // when index 0 is unavailable. let filter = match unsafe { TARGET_EXEC_COMM.get_ptr(0) } { - Some(ptr) => unsafe { *ptr }, + Some(ptr) => { + // SAFETY: get_ptr returned a valid pointer to the array value for this + // helper invocation; [u8; 16] is Copy. + unsafe { *ptr } + } None => return true, }; if filter[0] == 0 { @@ -93,6 +105,8 @@ pub fn sched_process_exec(ctx: TracePointContext) -> u32 { #[tracepoint(name = "sched_process_fork", category = "sched")] pub fn sched_process_fork(ctx: TracePointContext) -> u32 { let pid = current_tgid(); + // SAFETY: ALLOWED_PIDS is the statically declared BPF hash map; aya's map API + // performs verifier-compatible map lookup for the stack key. unsafe { if ALLOWED_PIDS.get(&pid).is_none() { return 0; @@ -106,6 +120,8 @@ pub fn sched_process_fork(ctx: TracePointContext) -> u32 { #[tracepoint(name = "sched_process_exit", category = "sched")] pub fn sched_process_exit(ctx: TracePointContext) -> u32 { let pid = current_tgid(); + // SAFETY: ALLOWED_PIDS is the statically declared BPF hash map; aya's map API + // performs verifier-compatible map lookup for the stack key. unsafe { if ALLOWED_PIDS.get(&pid).is_none() { return 0; @@ -119,5 +135,6 @@ pub fn sched_process_exit(ctx: TracePointContext) -> u32 { // Required by aya-bpf for panic handling in no_std #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { + // SAFETY: eBPF programs cannot unwind; this panic handler marks the path unreachable. unsafe { core::hint::unreachable_unchecked() } } diff --git a/ghostscope-process/src/module_probe.rs b/ghostscope-process/src/module_probe.rs index 0deb58f7..be29c4a7 100644 --- a/ghostscope-process/src/module_probe.rs +++ b/ghostscope-process/src/module_probe.rs @@ -49,6 +49,9 @@ impl ModuleProbe { let ino = meta.ino(); let metadata_cookie = ((dev & 0xffff_ffff) << 32) | (ino & 0xffff_ffff); + // SAFETY: The descriptor is a validated read-only regular file. This code + // only exposes the mapping immutably for object parsing; callers must not + // concurrently modify the mapped file while the mapping is alive. let mmap = unsafe { MmapOptions::new().map(&file)? }; Ok(Self { diff --git a/ghostscope-process/src/pinned_bpf_maps.rs b/ghostscope-process/src/pinned_bpf_maps.rs index ced07c03..160c674d 100644 --- a/ghostscope-process/src/pinned_bpf_maps.rs +++ b/ghostscope-process/src/pinned_bpf_maps.rs @@ -224,7 +224,9 @@ pub struct ProcModuleOffsetsValue { pub bss: u64, } +// SAFETY: ProcModuleKey is repr(C), Copy, and contains only plain integer fields. unsafe impl aya::Pod for ProcModuleKey {} +// SAFETY: ProcModuleOffsetsValue is repr(C), Copy, and contains only plain integer fields. unsafe impl aya::Pod for ProcModuleOffsetsValue {} #[repr(C)] @@ -233,6 +235,7 @@ pub struct PidAliasValue { pub proc_pid: u32, } +// SAFETY: PidAliasValue is repr(C), Copy, and contains only a plain integer field. unsafe impl aya::Pod for PidAliasValue {} impl ProcModuleOffsetsValue { diff --git a/ghostscope-process/src/sysmon.rs b/ghostscope-process/src/sysmon.rs index a16d372c..ebe78d64 100644 --- a/ghostscope-process/src/sysmon.rs +++ b/ghostscope-process/src/sysmon.rs @@ -505,6 +505,8 @@ fn run_sysmon_loop( while let Some(item) = rb.next() { had_event = true; if item.len() == core::mem::size_of::() { + // SAFETY: The ring buffer sample length was checked to match SysEvent; + // read_unaligned handles any alignment from the byte slice. let ev = unsafe { core::ptr::read_unaligned(item.as_ptr() as *const SysEvent) }; if let Err(e) = ProcessSysmon::handle_event(&mgr, &target, &pending, &ev) { tracing::debug!( @@ -556,6 +558,8 @@ fn run_sysmon_loop( copied += take; } if copied == raw.len() { + // SAFETY: raw is exactly the size of SysEvent and read_unaligned + // handles the byte array's alignment. let ev = unsafe { core::ptr::read_unaligned(raw.as_ptr() as *const SysEvent) }; diff --git a/ghostscope-ui/src/components/ebpf_panel/output/streaming_parser_bridge.rs b/ghostscope-ui/src/components/ebpf_panel/output/streaming_parser_bridge.rs index 4ee42459..06a59d0e 100644 --- a/ghostscope-ui/src/components/ebpf_panel/output/streaming_parser_bridge.rs +++ b/ghostscope-ui/src/components/ebpf_panel/output/streaming_parser_bridge.rs @@ -79,6 +79,8 @@ mod tests { }; // Convert header to bytes + // SAFETY: test_header lives for the duration of header_bytes and the slice + // length is exactly the size of TraceEventHeader. let header_bytes = unsafe { std::slice::from_raw_parts( &test_header as *const _ as *const u8, diff --git a/ghostscope/src/util.rs b/ghostscope/src/util.rs index 2a793b3d..acf3ef78 100644 --- a/ghostscope/src/util.rs +++ b/ghostscope/src/util.rs @@ -61,6 +61,7 @@ fn effective_capabilities() -> Option { /// Ensure the current process has the privileges required for eBPF interaction. /// Exits with an error message if neither root nor sufficient capabilities are present. pub fn ensure_privileges() { + // SAFETY: geteuid has no preconditions and does not dereference user pointers. let euid = unsafe { libc::geteuid() }; if euid == 0 { return;