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

Layering snmalloc on top of another allocator #697

Open
mjp41 opened this issue Nov 26, 2024 · 6 comments
Open

Layering snmalloc on top of another allocator #697

mjp41 opened this issue Nov 26, 2024 · 6 comments

Comments

@mjp41
Copy link
Member

mjp41 commented Nov 26, 2024

There are cases where it would be useful for snmalloc to pass certain requests to a different allocator.

  1. When overriding an allocator, being able to decide if an allocation is not from snmalloc and pass it to the original allocator.
  2. When integrating with systems such as GWP-Asan, here memory comes from a different source and structure, and wants to be called some of the time.

The documentation for GWP-Asan has

#ifdef INSTALL_GWP_ASAN_STUBS
  gwp_asan::GuardedPoolAllocator GWPASanAllocator;
#endif

void* YourAllocator::malloc(..) {
#ifdef INSTALL_GWP_ASAN_STUBS
  if (GWPASanAllocator.shouldSample(..))
    return GWPASanAllocator.allocate(..);
#endif

  // ... the rest of your allocator code here.
}

This I think should involve a concept of the shape:

class SecondaryAllocator
{
   void* alloc(size_t);
   void dealloc(void*);
};

We would put calls on the slow path for allocation

This would involve adding something like

void* result = SecondaryAllocator::alloc(size);
if (result != nullptr)
  return result;

For deallocation there would just be a change here:

  • // If p_tame is not null, then dealloc has been call on something
    // it shouldn't be called on.
    // TODO: Should this be tested even in the !CHECK_CLIENT case?
    snmalloc_check_client(
    mitigations(sanity_checks),
    p_tame == nullptr,
    "Not allocated by snmalloc.");

This would involve adding code like

SecondaryAllocator::free(p_tame);

We could then define a Default class as

class DefaultSecondaryAllocator
{
  SNMALLOC_FAST_PATH static void* alloc(size_t)
  {
    return nullptr;
  }

  SNMALLOC_FAST_PATH static void free(void* p)
  {
    // If p_tame is not null, then dealloc has been call on something 
    // it shouldn't be called on. 
    // TODO: Should this be tested even in the !CHECK_CLIENT case? 
    snmalloc_check_client( 
      mitigations(sanity_checks), 
      p_tame == nullptr, 
      "Not allocated by snmalloc."); 
  }
};

and for GWP something line

class GWPSecondaryAllocator
{
  SNMALLOC_FAST_PATH static void* alloc(size_t size)
  {
    if (SNMALLOC_UNLIKELY((GWPASanAllocator.shouldSample())
    {
       return GWPASanAllocator.allocate(size);
    }
    return nullptr;
  }

  SNMALLOC_FAST_PATH static void free(void* p)
  {
    GWPAsanAllocator.deallocate(p);
  }
};

Providing a SecondaryAllocator as a compile time option would allow us to integrate various systems cleanly.

Unknowns

  • How to actually integrate with GWPAsan builds
  • If this design works for more than just GWPAsan
  • Precise way to integrate the configuration of the SecondaryAllocator into the build. Perhaps part of class CommonConfig, and override by specific configurations.
@mjp41
Copy link
Member Author

mjp41 commented Nov 26, 2024

@nwf @davidchisnall @SchrodingerZhu any thoughts on this? Seems reasonably low effort to potential reward.

@SchrodingerZhu
Copy link
Collaborator

First, this does seem like a good idea.
I am not sure about GWPASan but maybe you can try scudo first. It is a purely standalone allocator without any dependency to c/c++ runtime. Hence it may be easy to integrate. Scudo itself also has a GWPASan flag but I did not try that before.

@SchrodingerZhu
Copy link
Collaborator

Actually, I checked the Scudo interface. API-wise I think GWPASan are easier to work with. Let me check more about how scudo is integrating GWPASan and I can report back.

@SchrodingerZhu
Copy link
Collaborator

@mjp41
Copy link
Member Author

mjp41 commented Nov 27, 2024

Thanks for investigating. Reading that file, we also need to do some integration around realloc/malloc_usable_size. I'm not sure if we can find the start of an object easily with GWP-Asan to get external_pointer working as well.

@SchrodingerZhu I won't have time to work on this for a while, so if you're interested you are welcome to have a go. Otherwise I'll try to find some time, but it might be a month or two.

@nwf
Copy link
Collaborator

nwf commented Dec 2, 2024

I see no real harm in the proposed refactoring to allow for compile-time pluggable behaviors here. Just to state the obvious: the branch on the malloc side should optimize away given the Default and we already have a branch on the free side. (Though some part of me does wonder if there's a way to composite snmalloc and more than one other allocator... but that probably requires that everyone involved understand the pagemap at least a little bit, and that's probably a bridge too far.)

One nit-picky, but just to say... in the example above, the DefaultSecondaryAllocator::free() does not have access to the would-be-domesticated p_tame. However it'd only be called if p_tame == null, so maybe its body is snmalloc_check_client(mitigations(sanity_checks), true, "Not allocated by snmalloc")?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants