38 ms·
A few years back I patched the memory allocator used by the Cloudflare Workers runtime to overwrite all memory with a static byte pattern on free, so that unini
by kentonv 10mo ago
A few years back I patched the memory allocator used by the Cloudflare Workers runtime to overwrite all memory with a static byte pattern on free, so that uninitialized allocations contain nothing interesting.
We expected this to hurt performance, but we were unable to measure any impact in practice.
Everyone still working in memory-unsafe languages should really just do this IMO. It would have mitigated this Mongo bug.
- tombert 10mo agoYou know, I never even considered doing that but it makes sense; whatever overhead that's incurred by doing that static byte pattern is still almost certainly minuscule compared to the overhead of something like a garbage collector.
- ddtaylor 10mo agoIMO the tradeoff that is important here is a few microseconds of time sanitizing the memory saves the millions of dollars of headache when memory unsafe languages fail (which happens regularly)
- tombert 10mo agoI agree. I almost feel like this should be like a flag in `free`. Like if you pass in 1 or something as a second argument (or maybe a `free_safe` function or something), it will automatically `memset` whatever it's freeing with 0's, and then do the normal freeing.
- yawaramin 10mo agohttps://news.ycombinator.com/item?id=46417221 https://news.ycombinator.com/item?id=46417221
- miki123211 10mo agoAlternatively, just make free do that by default, adding a fast_and_furious_free which doesn't do it, for the few hotspots where that tiny bit of performance is actually needed.
- sitkack 10mo agoThe default case should be the safe correct one, even if it “breaks” backward compatibility. Without it, we will forever be saddled with the design mistakes of the past.
- anonymars 10mo agoNon-deterministic latency is a drawback, but garbage collection is not inherently slower than manual memory management/reference counting/etc. Depending on the usage pattern it can be faster. It's a set of trade-offs
- dmitrygr 10mo agoFYI, at least in C/C++, the compiler is free to throw away assignments to any memory pointed to by a pointer if said pointer is about to be passed to free(), so depending on how you did this, no perf impact could have been because your compiler removed the assignment. This will even affect a call to memset() see here: https://godbolt.org/z/rMa8MbYox https://godbolt.org/z/rMa8MbYox
- shakna 10mo agoHowever, if you recast to volatile, the compiler will keep it: #include <stdlib.h> #include <string.h> void free(void* ptr); void not_free(void* ptr); void test_with_free(char* ptr) { ptr[5] = 6; void *(* volatile memset_v)(void *s, int c, size_t n) = memset; memset_v(ptr + 2, 3, 4); free(ptr); } void test_with_other_func(char* ptr) { ptr[5] = 6; void *(* volatile memset_v)(void *s, int c, size_t n) = memset; memset_v(ptr + 2, 3, 4); not_free(ptr); }
- maxlybbert 10mo agoNewer versions of C++ (and C, apparently) have functions so that the cast isn't necessary ( https://en.cppreference.com/w/c/string/byte/memset.html https://en.cppreference.com/w/c/string/byte/memset.html ).
- cperciva 10mo agoThat code is not guaranteed to work. Declaring memset_v as volatile means that the variable has to be read, but does not imply that the function must be called; the compiler is free to compile the function call as "tmp = memset_v; if (tmp != memset) tmp(...)" relying on its knowledge that in the likely case of equality the call can be optimized away.
- shakna 10mo agoWhilst the C standard doesn't guarantee it, both LLVM and GCC _do_. They have implementation-defined that it will work, so are not free to optimise it away. [0] https://llvm.org/docs/LangRef.html#llvm-memset-intrinsics https://llvm.org/docs/LangRef.html#llvm-memset-intrinsics [1] https://gitweb.git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/memset_explicit.c;hb=refs/heads/stable-202301 https://gitweb.git.savannah.gnu.org/gitweb/?p=gnulib.git;a=b...
- cperciva 10mo agoA few years back I patched the memory allocator used by the Cloudflare Workers runtime to overwrite all memory with a static byte pattern on free, so that uninitialized allocations contain nothing interesting. Note that many malloc implementations will do this for you given an appropriate environment, e.g. setting MALLOC_CONF to opt.junk=free will do this on FreeBSD.
- MuffinFlavored 10mo ago> OpenBSD uses 0xdb to fill newly allocated memory and 0xdf to fill memory upon being freed. This helps developers catch "use-before-initialization" (seeing 0xdb) and "use-after-free" (seeing 0xdf) bugs quickly. Looks like this is the default in OpenBSD.
- basilgohar 10mo agoI like this. The only information leaking is whether the memory range was previously used. I suppose you may want to control for that. I'd be surprised if OpenBSD didn't provide a flag to just freed memory to the same value as never allocated.
- cpach 10mo agoThis makes me curious. This bit of information – knowing whether the memory range was previously used or not – how could it be exploited?
- amomchilov 10mo agoRecent macOS versions zero out memory on free, which improves the efficacy of memory compression. Apparently it’s a net performance gain in the average case
- LoganDark 10mo agoI wonder if Apple Silicon has hardware acceleration for memory zeroing... Knowing Apple, I wouldn't be surprised.
- chuckadams 10mo agoARM in general does, or at least some modern variants. Various docs for Android and LLVM suggest it's part of the Memory Tagging Extension.
- dfc 10mo agoIs this the same as enabling `init_on_free=1` in the kernel?
- rectang 10mo agoZeroing memory should absolutely be the default behavior for any generic allocator in 2025. If you need better performance, write your own allocator optimized for your specific use case — it's not that hard. Besides, you if you don't need to clear old allocations, there are likely other optimizations you'll be able to find which would never fly in a system allocator.