Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Rust bindings leak emulators #1619

Closed
nemarci opened this issue May 18, 2022 · 13 comments
Closed

Rust bindings leak emulators #1619

nemarci opened this issue May 18, 2022 · 13 comments
Labels

Comments

@nemarci
Copy link

nemarci commented May 18, 2022

Steps to reproduce the bug:

  1. Create an emulator
  2. Add a hook to it
  3. Drop the emulator (just let it go out of scope)

Expected behaviour: all resources related to the emulator are freed.

Actual behaviour: the emulator is not freed.

The following code showcases the problem:

use std::thread;
use std::time::Duration;

use unicorn_engine::unicorn_const::{Arch, Mode, Permission};
use unicorn_engine::Unicorn;

pub fn code_hook(_: &mut Unicorn<()>, _: u64, _: u32) {}

fn main() {
    const MEM_SIZE: usize = 0x10000000; // 256 MB
    const PAGE_SIZE: usize = 0x1000;
    const START_ADDRESS: u64 = 0x10000;

    for i in 0..0x100 {
        println!("{i}");

        let mut emu = unicorn_engine::Unicorn::new(Arch::X86, Mode::MODE_64)
            .expect("failed to initialize Unicorn instance");
        emu.mem_map(START_ADDRESS, MEM_SIZE, Permission::ALL)
            .expect("failed to map code page");

        // Touch all pages to actually map the memory
        for address in (START_ADDRESS..START_ADDRESS + MEM_SIZE as u64).step_by(PAGE_SIZE) {
            emu.mem_write(address, &[0xcc]).unwrap();
        }

        emu.add_code_hook(0, u64::MAX, code_hook).unwrap();

        thread::sleep(Duration::from_secs(1));
        // emu is dropped here
    }
}

At each point in time, only one emulator should exist, so the memory usage should stay constant, around MEM_SIZE. Instead you will see that after each second, the memory usage increases by MEM_SIZE, indicating that the emulator is not freed.

The root cause of the problem is that the UnicornInner does not get dropped when Unicorn is dropped. This happens because inner is an Rc, which only gets dropped if its reference count is exactly one.

pub struct UnicornInner<'a, D> {
pub handle: uc_handle,
pub ffi: bool,
pub arch: Arch,
/// to keep ownership over the hook for this uc instance's lifetime
pub hooks: Vec<(ffi::uc_hook, Box<dyn ffi::IsUcHook<'a> + 'a>)>,
/// To keep ownership over the mmio callbacks for this uc instance's lifetime
pub mmio_callbacks: Vec<MmioCallbackScope<'a>>,
pub data: D,
}
/// Drop UC
impl<'a, D> Drop for UnicornInner<'a, D> {
fn drop(&mut self) {
if !self.ffi && !self.handle.is_null() {
unsafe { ffi::uc_close(self.handle) };
}
self.handle = ptr::null_mut();
}
}
/// A Unicorn emulator instance.
pub struct Unicorn<'a, D: 'a> {
inner: Rc<UnsafeCell<UnicornInner<'a, D>>>,
}

However, the add_code_hook function clones inner, increasing the reference count. This introduces a circluar reference, because the inner holds a reference to the hooks, but the hooks also hold a reference to inner; hence inner will never get dropped.

inner: self.inner.clone(),

The same issue holds for all add_*_hook functions, as well as mmio_map.

@wtdcode
Copy link
Member

wtdcode commented May 20, 2022

Acknowledged, but I'm not rust expert and not confident enough to post a fix. Could you draft a PR?

@wtdcode wtdcode added the bug label May 20, 2022
@bet4it
Copy link
Contributor

bet4it commented May 23, 2022

@domenukk can you fix it?

bet4it added a commit to bet4it/unicorn that referenced this issue Jun 22, 2022
bet4it added a commit to bet4it/unicorn that referenced this issue Jun 23, 2022
bet4it added a commit to bet4it/unicorn that referenced this issue Jun 29, 2022
@github-actions
Copy link

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days.

@github-actions github-actions bot added the stale label Jul 23, 2022
@wtdcode wtdcode removed the stale label Jul 24, 2022
@expend20
Copy link

expend20 commented Sep 6, 2022

Yeah also noticed this, with next code it leaks roughly 1GB at a time.

use unicorn_engine::unicorn_const as ucc;
use unicorn_engine::Unicorn;

fn leaks() {
    let uni = Unicorn::new(ucc::Arch::X86, ucc::Mode::MODE_64);
    if uni.is_err() {
        println!("Unable to create unicorn instance");
        return;
    }
    let mut emu = uni.unwrap();
    emu.add_mem_hook(
        ucc::HookType::MEM_UNMAPPED,
        0,
        u64::MAX,
        |_uc, _access, _addr, _size, _value| {
            true
        },
    ).unwrap();
}
fn main() {
    for i in 0..30 {
        leaks();
        println!("Iteration {}, check ram usage...", i);
        // sleep for 1 second
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}

vmconnect_RqEQUyu2bU

@expend20
Copy link

expend20 commented Sep 6, 2022

Yeah also noticed this, with next code it leaks roughly 1GB at a time.

use unicorn_engine::unicorn_const as ucc;
use unicorn_engine::Unicorn;

fn leaks() {
    let uni = Unicorn::new(ucc::Arch::X86, ucc::Mode::MODE_64);
    if uni.is_err() {
        println!("Unable to create unicorn instance");
        return;
    }
    let mut emu = uni.unwrap();
    emu.add_mem_hook(
        ucc::HookType::MEM_UNMAPPED,
        0,
        u64::MAX,
        |_uc, _access, _addr, _size, _value| {
            true
        },
    ).unwrap();
}
fn main() {
    for i in 0..30 {
        leaks();
        println!("Iteration {}, check ram usage...", i);
        // sleep for 1 second
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}

vmconnect_RqEQUyu2bU vmconnect_RqEQUyu2bU

Actually remove_hook() solves the problem for me, could you also try @nemarci ?

@nemarci
Copy link
Author

nemarci commented Sep 6, 2022

Yeah also noticed this, with next code it leaks roughly 1GB at a time.

use unicorn_engine::unicorn_const as ucc;
use unicorn_engine::Unicorn;

fn leaks() {
    let uni = Unicorn::new(ucc::Arch::X86, ucc::Mode::MODE_64);
    if uni.is_err() {
        println!("Unable to create unicorn instance");
        return;
    }
    let mut emu = uni.unwrap();
    emu.add_mem_hook(
        ucc::HookType::MEM_UNMAPPED,
        0,
        u64::MAX,
        |_uc, _access, _addr, _size, _value| {
            true
        },
    ).unwrap();
}
fn main() {
    for i in 0..30 {
        leaks();
        println!("Iteration {}, check ram usage...", i);
        // sleep for 1 second
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}

vmconnect_RqEQUyu2bU

    [
      
        ![vmconnect_RqEQUyu2bU](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif)
      
    ](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif)
    
    
      
        
          
        
        
          
          
        
      
      [
        
          
        
      ](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif)
    
   [ ![vmconnect_RqEQUyu2bU](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif) ](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif)
  
    [
      
        ![vmconnect_RqEQUyu2bU](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif)
      
    ](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif)
    
    
      
        
          
        
        
          
          
        
      
      [
        
          
        
      ](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif)
    
   [ ](https://user-images.githubusercontent.com/36543551/188592558-161c1776-192b-4a72-96cd-00c2012a49a4.gif)

Actually remove_hook() solves the problem for me, could you also try @nemarci ?

You're right, explicitly removing all hooks before dropping the emulator solves the issue. However, I think this is rather a workaround than a proper solution.

@bet4it
Copy link
Contributor

bet4it commented Sep 6, 2022

@expend20 @nemarci Could you check if #1632 solves this problem? If so, you can Approve this PR.

@expend20
Copy link

expend20 commented Sep 8, 2022

hey @bet4it, thanks for the fix. I can confirm it's working (not leaking the memory on my sample)

@github-actions
Copy link

github-actions bot commented Nov 8, 2022

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days.

@github-actions github-actions bot added the stale label Nov 8, 2022
@domenukk
Copy link
Contributor

domenukk commented Nov 8, 2022

Not fixed yet

@github-actions github-actions bot removed the stale label Nov 9, 2022
@github-actions
Copy link

github-actions bot commented Jan 9, 2023

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days.

@smmalis37
Copy link

Just hit this problem, found this issue, switched to the dev branch, and it seems to have gone away. Thanks!

@github-actions github-actions bot removed the stale label Jan 28, 2023
@wtdcode
Copy link
Member

wtdcode commented Sep 22, 2024

Closing as fixed.

@wtdcode wtdcode closed this as completed Sep 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants