Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  test.rs   Sprache: unbekannt

 
// This binary shouldn't be under /src, but under /tests, but that is
// currently not possible (https://github.com/rust-lang/cargo/issues/4356)

type Error = Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>;
pub type Result<T> = std::result::Result<T, Error>;

#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux {
    use super::*;
    use minidump_writer::{
        minidump_writer::STOP_TIMEOUT, module_reader, ptrace_dumper::PtraceDumper,
        LINUX_GATE_LIBRARY_NAME,
    };
    use nix::{
        sys::mman::{mmap_anonymous, MapFlags, ProtFlags},
        unistd::getppid,
    };

    macro_rules! test {
        ($x:expr, $errmsg:expr) => {
            if !$x {
                return Err($errmsg.into());
            }
        };
    }

    fn test_setup() -> Result<()> {
        let ppid = getppid();
        PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
        Ok(())
    }

    fn test_thread_list() -> Result<()> {
        let ppid = getppid();
        let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
        test!(!dumper.threads.is_empty(), "No threads");
        test!(
            dumper
                .threads
                .iter()
                .filter(|x| x.tid == ppid.as_raw())
                .count()
                == 1,
            "Thread found multiple times"
        );

        test!(
            dumper
                .threads
                .iter()
                .any(|thread| thread.name.as_deref() == Some("sighup-thread")),
            "Failed to locate and/or stop sighup-thread"
        );

        Ok(())
    }

    fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> {
        use minidump_writer::mem_reader::MemReader;

        let ppid = getppid().as_raw();
        let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
        dumper.suspend_threads()?;

        // We support 3 different methods of reading memory from another
        // process, ensure they all function and give the same results

        let expected_stack = 0x11223344usize.to_ne_bytes();
        let expected_heap = 0x55667788usize.to_ne_bytes();

        let validate = |reader: &mut MemReader| -> Result<()> {
            let mut val = [0u8; std::mem::size_of::<usize>()];
            let read = reader.read(stack_var, &mut val)?;
            assert_eq!(read, val.len());
            test!(val == expected_stack, "stack var not correct");

            let read = reader.read(heap_var, &mut val)?;
            assert_eq!(read, val.len());
            test!(val == expected_heap, "heap var not correct");

            Ok(())
        };

        // virtual mem
        {
            let mut mr = MemReader::for_virtual_mem(ppid);
            validate(&mut mr)
                .map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?;
        }

        // file
        {
            let mut mr = MemReader::for_file(ppid)
                .map_err(|err| format!("failed to open `/proc/{ppid}/mem`: {err}"))?;
            validate(&mut mr)
                .map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?;
        }

        // ptrace
        {
            let mut mr = MemReader::for_ptrace(ppid);
            validate(&mut mr)
                .map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?;
        }

        let stack_res =
            PtraceDumper::copy_from_process(ppid, stack_var, std::mem::size_of::<usize>())?;

        test!(stack_res == expected_stack, "stack var not correct");

        let heap_res =
            PtraceDumper::copy_from_process(ppid, heap_var, std::mem::size_of::<usize>())?;

        test!(heap_res == expected_heap, "heap var not correct");

        dumper.resume_threads()?;
        Ok(())
    }

    fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> {
        let ppid = getppid();
        let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
        dumper
            .find_mapping(addr1)
            .ok_or("No mapping for addr1 found")?;

        dumper
            .find_mapping(addr2)
            .ok_or("No mapping for addr2 found")?;

        test!(dumper.find_mapping(0).is_none(), "NULL found");
        Ok(())
    }

    fn test_file_id() -> Result<()> {
        let ppid = getppid().as_raw();
        let exe_link = format!("/proc/{ppid}/exe");
        let exe_name = std::fs::read_link(exe_link)?.into_os_string();
        let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
        dumper.suspend_threads()?;
        let mut found_exe = None;
        for (idx, mapping) in dumper.mappings.iter().enumerate() {
            if mapping.name.as_ref().map(|x| x.into()).as_ref() == Some(&exe_name) {
                found_exe = Some(idx);
                break;
            }
        }
        let idx = found_exe.unwrap();
        let module_reader::BuildId(id) = dumper.from_process_memory_for_index(idx)?;
        dumper.resume_threads()?;
        assert!(!id.is_empty());
        assert!(id.iter().any(|&x| x > 0));
        Ok(())
    }

    fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Result<()> {
        // Now check that PtraceDumper interpreted the mappings properly.
        let dumper = PtraceDumper::new(getppid().as_raw(), STOP_TIMEOUT, Default::default())?;
        let mut mapping_count = 0;
        for map in &dumper.mappings {
            if map
                .name
                .as_ref()
                .map_or(false, |name| name.to_string_lossy().starts_with(&path))
            {
                mapping_count += 1;
                // This mapping should encompass the entire original mapped
                // range.
                assert_eq!(map.start_address, mapped_mem);
                assert_eq!(map.size, mem_size);
                assert_eq!(0, map.offset);
            }
        }
        assert_eq!(1, mapping_count);
        Ok(())
    }

    fn test_linux_gate_mapping_id() -> Result<()> {
        let ppid = getppid().as_raw();
        let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
        let mut found_linux_gate = false;
        for mapping in dumper.mappings.clone() {
            if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) {
                found_linux_gate = true;
                dumper.suspend_threads()?;
                let module_reader::BuildId(id) =
                    PtraceDumper::from_process_memory_for_mapping(&mapping, ppid)?;
                test!(!id.is_empty(), "id-vec is empty");
                test!(id.iter().any(|&x| x > 0), "all id elements are 0");
                dumper.resume_threads()?;
                break;
            }
        }
        test!(found_linux_gate, "found no linux_gate");
        Ok(())
    }

    fn test_mappings_include_linux_gate() -> Result<()> {
        let ppid = getppid().as_raw();
        let dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
        let linux_gate_loc = dumper.auxv.get_linux_gate_address().unwrap();
        test!(linux_gate_loc != 0, "linux_gate_loc == 0");
        let mut found_linux_gate = false;
        for mapping in &dumper.mappings {
            if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) {
                found_linux_gate = true;
                test!(
                    linux_gate_loc == mapping.start_address.try_into()?,
                    "linux_gate_loc != start_address"
                );

                // This doesn't work here, as we do not test via "fork()", so the addresses are different
                // let ll = mapping.start_address as *const u8;
                // for idx in 0..header::SELFMAG {
                //     let mag = unsafe { std::ptr::read(ll.offset(idx as isize)) == header::ELFMAG[idx] };
                //     test!(
                //         mag,
                //         format!("ll: {} != ELFMAG: {} at {}", mag, header::ELFMAG[idx], idx)
                //     )?;
                // }
                break;
            }
        }
        test!(found_linux_gate, "found no linux_gate");
        Ok(())
    }

    fn spawn_and_wait(num: usize) -> Result<()> {
        // One less than the requested amount, as the main thread counts as well
        for _ in 1..num {
            std::thread::spawn(|| {
                println!("1");
                loop {
                    std::thread::park();
                }
            });
        }
        println!("1");
        loop {
            std::thread::park();
        }
    }

    fn spawn_name_wait(num: usize) -> Result<()> {
        // One less than the requested amount, as the main thread counts as well
        for id in 1..num {
            std::thread::Builder::new()
                .name(format!("thread_{}", id))
                .spawn(|| {
                    println!("1");
                    loop {
                        std::thread::park();
                    }
                })?;
        }
        println!("1");
        loop {
            std::thread::park();
        }
    }

    fn spawn_mmap_wait() -> Result<()> {
        let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap();
        let memory_size = std::num::NonZeroUsize::new(page_size.unwrap() as usize).unwrap();
        // Get some memory to be mapped by the child-process
        let mapped_mem = unsafe {
            mmap_anonymous(
                None,
                memory_size,
                ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
                MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON,
            )
            .unwrap()
        };

        println!("{} {}", mapped_mem.as_ptr() as usize, memory_size);
        loop {
            std::thread::park();
        }
    }

    fn spawn_alloc_wait() -> Result<()> {
        let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap();
        let memory_size = page_size.unwrap() as usize;

        let mut values = Vec::<u8>::with_capacity(memory_size);
        for idx in 0..memory_size {
            values.push((idx % 255) as u8);
        }

        println!("{:p} {}", values.as_ptr(), memory_size);
        loop {
            std::thread::park();
        }
    }

    fn create_files_wait(num: usize) -> Result<()> {
        let mut file_array = Vec::<tempfile::NamedTempFile>::with_capacity(num);
        for id in 0..num {
            let file = tempfile::Builder::new()
                .prefix("test_file")
                .suffix::<str>(id.to_string().as_ref())
                .tempfile()
                .unwrap();
            file_array.push(file);
            println!("1");
        }
        println!("1");
        loop {
            std::thread::park();
            // This shouldn't be executed, but we put it here to ensure that
            // all the files within the array are kept open.
            println!("{}", file_array.len());
        }
    }

    pub(super) fn real_main(args: Vec<String>) -> Result<()> {
        match args.len() {
            1 => match args[0].as_ref() {
                "nop" => Ok(()),
                "file_id" => test_file_id(),
                "setup" => test_setup(),
                "thread_list" => test_thread_list(),
                "mappings_include_linux_gate" => test_mappings_include_linux_gate(),
                "linux_gate_mapping_id" => test_linux_gate_mapping_id(),
                "spawn_mmap_wait" => spawn_mmap_wait(),
                "spawn_alloc_wait" => spawn_alloc_wait(),
                _ => Err("Len 1: Unknown test option".into()),
            },
            2 => match args[0].as_ref() {
                "spawn_and_wait" => {
                    let num_of_threads: usize = args[1].parse().unwrap();
                    spawn_and_wait(num_of_threads)
                }
                "spawn_name_wait" => {
                    let num_of_threads: usize = args[1].parse().unwrap();
                    spawn_name_wait(num_of_threads)
                }
                "create_files_wait" => {
                    let num_of_files: usize = args[1].parse().unwrap();
                    create_files_wait(num_of_files)
                }
                _ => Err(format!("Len 2: Unknown test option: {}", args[0]).into()),
            },
            3 => {
                if args[0] == "find_mappings" {
                    let addr1: usize = args[1].parse().unwrap();
                    let addr2: usize = args[2].parse().unwrap();
                    test_find_mappings(addr1, addr2)
                } else if args[0] == "copy_from_process" {
                    let stack_var: usize = args[1].parse().unwrap();
                    let heap_var: usize = args[2].parse().unwrap();
                    test_copy_from_process(stack_var, heap_var)
                } else {
                    Err(format!("Len 3: Unknown test option: {}", args[0]).into())
                }
            }
            4 => {
                if args[0] == "merged_mappings" {
                    let path = &args[1];
                    let mapped_mem: usize = args[2].parse().unwrap();
                    let mem_size: usize = args[3].parse().unwrap();
                    test_merged_mappings(path.to_string(), mapped_mem, mem_size)
                } else {
                    Err(format!("Len 4: Unknown test option: {}", args[0]).into())
                }
            }
            _ => Err("Unknown test option".into()),
        }
    }
}

#[cfg(target_os = "windows")]
mod windows {
    use super::*;
    use std::mem;

    #[link(name = "kernel32")]
    extern "system" {
        pub fn GetCurrentProcessId() -> u32;
        pub fn GetCurrentThreadId() -> u32;
        pub fn GetCurrentThread() -> isize;
        pub fn GetThreadContext(thread: isize, context: *mut crash_context::CONTEXT) -> i32;
    }

    #[inline(never)]
    pub(super) fn real_main(args: Vec<String>) -> Result<()> {
        let exception_code = u32::from_str_radix(&args[0], 16).unwrap();

        // Generate the exception and communicate back where the exception pointers
        // are
        unsafe {
            let mut exception_record: crash_context::EXCEPTION_RECORD = mem::zeroed();
            let mut exception_context = std::mem::MaybeUninit::uninit();

            let pid = GetCurrentProcessId();
            let tid = GetCurrentThreadId();

            GetThreadContext(GetCurrentThread(), exception_context.as_mut_ptr());

            let mut exception_context = exception_context.assume_init();

            let exception_ptrs = crash_context::EXCEPTION_POINTERS {
                ExceptionRecord: &mut exception_record,
                ContextRecord: &mut exception_context,
            };

            exception_record.ExceptionCode = exception_code as _;

            let exc_ptr_addr = &exception_ptrs as *const _ as usize;

            println!("{pid} {exc_ptr_addr} {tid} {exception_code:x}");

            // Wait until we're killed
            loop {
                std::thread::park();
            }
        }
    }
}

#[cfg(target_os = "macos")]
mod mac {
    use super::*;
    use std::time::Duration;

    #[inline(never)]
    pub(super) fn real_main(args: Vec<String>) -> Result<()> {
        let port_name = args.get(0).ok_or("mach port name not specified")?;
        let exception: u32 = args.get(1).ok_or("exception code not specified")?.parse()?;

        let client =
            crash_context::ipc::Client::create(&std::ffi::CString::new(port_name.clone())?)?;

        std::thread::Builder::new()
            .name("test-thread".to_owned())
            .spawn(move || {
                #[inline(never)]
                fn wait_until_killed(client: crash_context::ipc::Client, exception: u32) {
                    // SAFETY: syscalls
                    let cc = unsafe {
                        crash_context::CrashContext {
                            task: mach2::traps::mach_task_self(),
                            thread: mach2::mach_init::mach_thread_self(),
                            handler_thread: mach2::port::MACH_PORT_NULL,
                            exception: Some(crash_context::ExceptionInfo {
                                kind: exception,
                                code: 0,
                                subcode: None,
                            }),
                        }
                    };

                    // Send the crash context to the server and wait for it to
                    // finish dumping, we should be killed shortly afterwards
                    client
                        .send_crash_context(
                            &cc,
                            Some(Duration::from_secs(2)),
                            Some(Duration::from_secs(5)),
                        )
                        .expect("failed to send crash context/receive ack");

                    // Wait until we're killed
                    loop {
                        std::thread::park();
                    }
                }

                wait_until_killed(client, exception)
            })
            .unwrap()
            .join()
            .unwrap();

        Ok(())
    }
}

fn main() -> Result<()> {
    let args: Vec<_> = std::env::args().skip(1).collect();

    cfg_if::cfg_if! {
        if #[cfg(any(target_os = "linux", target_os = "android"))] {
            linux::real_main(args)
        } else if #[cfg(target_os = "windows")] {
            windows::real_main(args)
        } else if #[cfg(target_os = "macos")] {
            mac::real_main(args)
        } else {
            unimplemented!();
        }
    }
}

[ Dauer der Verarbeitung: 0.30 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge