[RFC] Experimental implementation of P2719 - Type-aware allocation and deallocation functions (original) (raw)

However in your case, you are intentionally overriding the global allocators so it would be assumed that your template and non-template global operators would agree on the underlying allocators that are to be used.

Thanks for the explanation, this all makes sense to me. PartitionAlloc does attempt to override all the different types.

The model we expect for global allocators is a template declaration that converts the compile time type into whatever your allocator wants/needs and passes that to a non-template entry point.

Makes sense, and that is in essence what we are doing but we had hoped that perhaps there was a way to ease the requirement (knowing that it isn’t how templates normally work). Because other operator new’s don’t have to be visible to be used. I.E. in allocator_shim_override_cpp_symbols.h we currently override all operator new functions. (including all the throw/nothrow variations), and this is only included in a single cc file (on most platforms) allocator_shim.cc. Of course MacOS is different here where we replace malloc directly.

This isn’t absolutely critical for many use cases, Chrome could obviously just have some sort of super base include that then gets directly included or transitively included, but it does feel different then other operator new since normally you don’t need to #include <new>.

But this might mean we can’t properly automatically infer types for things deep inside third_party code.

Could you give a more concrete example of how the allocators are defined/used?

In Chrome almost all memory is allocated through PartitionAlloc without any class explicitly stating so. In fact you typically need to explicitly set an allocator to disable this behaviour. This is done as above by overriding basically all allocation functions new/malloc/realloc/etc. Obviously if a third_party defines their own allocator and it avoids the hooks then it likely is possible to avoid being included in PartitionAlloc.

Basically the scenario is something like.

One example is quarantining the pointer to prevent UaF even after a free call until all references have disappeared. This requires of course that the pointer is wrapped in Chrome’s raw_ptr and thus only applies on the boundary area where we could easily #include "partition_alloc_new"

Hence Skia hasn’t defined any class scoped new/delete.

Another example would be using this to decide to sample SKIA objects more heavily for UaF in Chrome’s various UaF detectors (GWP-asan or Chrome’s ELUD). This could work even for objects that aren’t inside chrome’s code and thus don’t have any #include "partition_alloc_new", but I suspect right now we just don’t with the current proposal (since the template function isn’t visible).

I believe this also creates a little bit of an opening for some objects not to be protected? Correct me if I’m wrong but if we want to say “All SkBitmaps get this protection”. And we’re using a template unconstrained operator new. We can’t actually say that with this implementation of P2719 because any new SkBitmaps that happen deep inside Skia likely can’t see the templated function and will call the regular operator new? So instead we would have to say “Some SkBitmaps get this protection”?

Re constexpr:

We’d like to say “All objects allocated from SKIA should get this protection”. The easiest way I can think to do that would be to have the file path of the object’s definition and simply look for “third_party/skia”, using construct_type_info<T> and __PRETTY_FUNCTION__ is serviceable but a bit tricky since the language doesn’t give us many constexpr non rtti ways to inspect the type provided.

Having name of type at compile time and std::source_location of call site would be enough I think for almost any possible decision we’d want to make for example.

The typed memory RFC or the Darwin specific malloc_typed_malloc , etc entry points?

I was thinking about typed memory RFC more generally than just Darwin. I think it is fine that it is out of scope. I am curious about the statement that having the TMO model (I only know TMO from your talk at llvm conference) offload the computation onto the compiler might make it harder to override. if you allow someone to define a function and provide it to the compiler in some dynamic (or compile time) way.

// I.E. inside clang
// See call to malloc with some sizeof(struct X)
Call typed_malloc(sizeof(struct X), overridable_fn_get_type_descriptor(getQualifiedTypeAsString(), decl.getFileName));

Users could provide the function in a single source file while compiling clang for example, seems possible to then create a mapping.

Perhaps even for operator new such a system could work at the cost of an additional const integer being passed at run time, which could be optimized away if no one uses it. It would remove the requirement for templated new since you wouldn’t be using them and instead they would just be the same as other operator new functions.

// clang sees a call to new
// call
new(overridable_fn_get_type_descriptor(getQualifiedTypeAsString(), decl.getFileName), size);