Title: Best practices for memory safety across FFI boundaries?

The Context and Setup
Hey everyone, I am running into a specific memory architecture challenge and could use some advice on the Odin way to handle it. I really appreciate the tracking allocator for catching leaks, and the native slice bounds checking is very useful for my internal logic. My current projects involve building dynamic libraries that act as a bridge to a Python host. Because I am crossing the external boundary, I am forced to drop down into raw pointers before I can cast them back into safe slices.

The Memory Problem
The tracking allocator accurately logs my Odin allocations, but it cannot intercept if Python passes a bad payload. If that external payload causes a buffer overrun on the memory via raw pointer math, the tracking allocator misses it. When working in other environments with strict architectural rules, I usually rely on injecting a dynamic shadow memory tool alongside the library to catch these violations. Having that memory shield at the boundary catches threats that purely logical tracking might overlook.

The Architecture Question
I believe that Odin bypasses the shared C runtime by default and talks directly to the OS memory APIs. That is a solid design choice, but it means the standard shadow memory injection trick does not work because there is no shared pipeline for the tool to hook into. Is there an established pattern or specialized allocator in the ecosystem used to introduce memory poisoning at this boundary? Or is the strictly accepted practice here just very aggressive manual bounds validation at the entry points before casting to a slice?

Concrete FFI Threat Examples
To give some concrete examples, here are the types of memory threats that can easily slip past logical tracking when raw pointers cross the external boundary. These include (1) heap buffer overflows and underflows, (2) heap use after free, (3) stack use after return, (4) stack buffer overflows, (5) use after scope, (6) global variable buffer overflows, (7) intra object buffer overflows, and (8) cross boundary allocator wild frees.

When using my other environments (C23 with ASan) coupled with strict tools, the system successfully catches all eight of these specific threats at the boundary.

I am simply trying to find the native Odin ways to protect against these numbered items and achieve that exact same level of auditing safety.

1 Like

…not much foot traffic here…
I will take this as there is no solution to this and I will have to be on guard for these eight serious threats. Discord server had no suggestions either.

I will try a custom allocator with hard limits on the page edge with a page guard, using that as a poison feature but that will not cover all eight listed above, but will be better than nothing with FFI boundary safety.

I was gonna suggest try using a growing arena allocator, but I was not sure I understood your needs well enough to associate the recommendation to specifics.

Maybe one for each category of allocation. Might even try a non-growing arena, and rely on errors returned for failing to allocate as a way to detect and react. I’m sure all the allocating type procedures will provide an allocation error if there is one.

In my fantasies, I’ve though about how I would go about creating a script engine similar to Python that would allow for Odin syntax to run as interpreted code. I’ve thought that if I ever went down that road, I’d use arena allocators for everything, regardless of what the script tries to do. Basically funnel everything into an allocation scheme that is manageable and predictable. Your question made me think of this, but was not sure how far off base my impression was.

2 Likes

I’ve come to similar conclusions, though I’m still in the early research phase on this. I’m currently digging into the fundamentals of how a custom allocator (potentially with poison/guards) might work, but I’m still navigating the learning curve of Odin’s runtime and implicit context system, as well as the nuances of pointer sharing.

I’m mostly just mapping out the feasibility right now and trying to find the ā€˜Odin way’ to handle this. My goal is to eventually bridge the gap to get that same ā€˜Sanitizer-level’ auditing I get with ASan, which is currently catching OOB issues from Python that the standard tracking allocator doesn’t see. I’ll be sure to share the implementation if I can get a stable prototype running.

Lastly, I really appreciate you taking the time to respond. You’re the only one who has given me a meaningful lead on this across both Discord and the forums, suspect it’s a bit of a niche/advanced topic for most, so thank you for the insight.

1 Like

What is and continues to work for me is writing smaller little programs to do things for me and forcing myself to use proper memory management even when technically it is not needed (i.e. run, output and quit applications). The thing that really kicked it in for me, was when I realized I was writing the same scripts for both Windows in PS and Linux in bash, and lamenting the syntax for both (and the fact that I was duplicating my effort every time I wanted to add functionality). I decided to write command-lets in Odin that could run in both environments. It’s more code than a script, but way more fun to do. Plus I learned many Odin best practices along the way.

For the memory system, I wonder if creating memory ā€œcollectorsā€ backed by arenas would be a useful way to visualize the way forward? Something like, String_Collector, Dynamic_Array_Collector, Random_Foo_Collector, etc…

Heres some reading material for your edification. Some lessons learned on how memory can be ā€œpoisonedā€.

Mistakes and cool things to do with arena allocators

Mistakes and cool things to do with arena allocators video

2 Likes

I’m curious if you can elaborate more on the workflow and tools you use. Are they specific to C, or could they work with Odin code as well?

Have you looked into doing an approach like Fil-C? It’s still not clear to me if this would require changes to the Odin language or if you could do all the Fil-C steps at your FFI boundary with Odin as is. A simplified model of Fil-C outlines an approach to implementing a naive Fil-C yourself at the high level language level. From the article:

Just like you can run C/C++ code under ASan to find memory bugs, you can run it under Fil-C to find memory bugs.

The information regarding Fil C is relevant to the technical limitations observed at the Odin and Python interface. This assessment is based on research and documented compiler errors, and while I am still acquiring knowledge in this area, I believe the following details are accurate. In a C23 and NumPy configuration, all components utilize the standard C Runtime. AddressSanitizer functions by intercepting memory allocation calls, which allows for consistent tracking across the interface.

Odin utilizes a custom runtime and does not rely on the standard C library for memory operations. AddressSanitizer is unable to monitor these allocations because they bypass the standard interception points. When the Python host process passes a memory address to the Odin library, the sanitizer cannot verify the source of that data. This lack of metadata prevents the tool from validating memory once it crosses the foreign function interface.

Odin compiler symbols prevent the static linking of object files, which necessitates the use of a dynamic library for Python integration. This configuration isolates the memory space and prevents the identification of (1) heap overflows (2) stack overflows (3) global overflows (4) use after free dangling pointers (5) use after return dangling pointers (6) use after scope dangling pointers (7) initialization order errors and (8) memory leaks.

Fil C utilizes pointers that include boundary metadata, similar to the internal slice structures in Odin. Implementing this at the interface would require passing complete structures rather than raw addresses. Is there any record of using complex structures across the interface to maintain this context?