System memory allocator free operation zeroes out deallocated blocks in iOS 16
mjtsai.com
mjtsai.com
The trouble with 0 initialization is it can accidentally be correct, or can look correct when it isn't. When your pointer is pointing to 0xDEADBEEF, odds are 99.9999% that the data wasn't initialized.
D does a similar thing. The default initializer for floating point variables is NaN, not zero. There was a debate about this in the D forums.
https://digitalmars.com/d/archives/digitalmars/D/Movement_ag...
My position was pretty clear - default initializations should not be accidentally correct.
I think they stuck with 0x0 because it keeps the performance of any calloc()-using object allocator the same (just moves the "when" of zeroing the chunk).
That’s what I’d think, too, but I wouldn’t be shocked if someone said they had some implementation detail which made it easier to work with zeroes. I know ARM has pretty good performance zeroing memory but that’s exactly the kind of thing where their hardware team might optimize what is now an extremely common operation.
It sounds like the calloc implementation is _assuming_ that the underlying alloc always zeros, so essentially (ignore overflow check) calloc is doing something like forward directly to malloc(size*count). So I think it _changes_ the exploit path to something new.
void f(void) {
obj *a;
a = calloc(1, sizeof(a));
g(a);
}
void g(obj *o) {
if (o->callback) {
o->callback();
}
}
it's contrived for simplicity, but suppose obj had a function pointer in it and just after allocation the object is passed to another routine which does some work on the object and checks if the callback exists and calls it if it does. In a single threaded application, you would not be able to target the function pointer here because nothing interesting happens between calloc and the invocation since the if check will always fail due to it being zero'd from calloc.Now, as you mention, if calloc didn't zero, an attacker could scribble over what they think will be the next allocated slot in the heap (either by targeting a partially used page or an entirely unused page that's just being kept in malloc's retainer) and write a value into the callback pointer field. Now when a new object is allocated there, we end up using the corrupted data and invoking the evil pointer, and the rest is history.
It's a bit of a weirder exploit path but honestly with how ridiculous UAF flows have gotten with modern mitigations on iOS, it's hardly the worst thing I've ever seen and I suspect someone will get decent mileage out of it
"Not accidentally correct" seems difficult in the generic case (think a 1 bit flag; of course for an int 0 is likely "more expected" than any other value); NaN for floats however is a nice variant that should work pretty well. Is it signaling or quiet?
We do what we can. D also uses 0xFF for char default initialization (an invalid code unit).
I'd love to see hardware having a "offloaded memory guard" flag: Mark in-use memory with it and get a OMG trap on any memory access with the flag not set (of course totally transparent with hardware acceleration). Too bad this is only simple on paper, where there is no such thing as (among others) memory latency.
For instance, I can imagine a program which accidentally follows a dangling pointer to an already freed structure, and reads another pointer from within that structure, not crashing because it ends up checking this later pointer against NULL... until the stars align and the memory used by the freed structure has been returned to the operating system, or overwritten by a later allocation.
[1] https://clang.llvm.org/docs/AddressSanitizer.html
[2] https://developer.apple.com/documentation/xcode/diagnosing-m...
It should be part any project from the start, run as much as possible with ASAN when the performance hit can be afforded.
It was actually in the link the parent posted. I just missed it at first.
Still, in some cases you have some tiny uninitialized member for example that is much harder to find. So with this malloc it might even cover those more.
On Linux, running `env MALLOC_PERTURB_=43605 myprogram` will do the same.
Some gibberish is better than others. For instance, on x86 (64-bit, 32-bit, or even 16-bit), filling with the 0xcc byte means that, if executed as code, it will trap (0xcc is the single-byte INT3 "single-step breakpoint" instruction). And for most Unix-style operating systems, which put the kernel in the high half of the address space, the filler bytes should have the high bit set, so that treating them as pointers will trap (the pointer will have its high bit set, pointing to kernel memory, which user space is forbidden from accessing).
The Linux setup where you can choose your own scribbles seems to meet your needs no?
Trying to execute off the heap is going to trap anyways.
As I mentioned, only until that freed block is reused for a new allocation, which means it will no longer be all-zeros; or until the memory allocator decides it's time to unmap that region, which means it will trap (SIGSEGV or similar).
When memory is unmapped, caches of memory mappings of other threads are discarded, via inter-core interrupts. Then they get misses until their cache is restored.
In what might be a multi-thread program, you don't fool with the memory map without very good reasons. Mapping new pages, or marking a page r/o or r/w is OK; anything else, probably not.
ARM architecture does not need to interrupt for TLB shootdown.
Is it really a problem? Failing harder and earlier is a good thing if it means the bug has more chances of being detected and fixed.
"the same way some programs currently accidentally depend on freeing memory not immediately overwriting it."
Maybe a dumb question, but what do you mean by this? Could you give a C/C++ pseudo-example? free(f);
f->close();
in code that does a few malloc, if/else’s, etc. between those calls. Stuff like that gets hard to spot in more complex code, especially in the presence of C++ copy constructors, move constructors, etc, where the compiler helpfully inserts that call to free for you.Bugs in copy constructors also can lead to double frees when the destructors of an object and its copy both call delete on a pointer that they think they exclusively own. Between those two destructor calls, you can easily get a use after free. Example: https://stackoverflow.com/a/64014035
If the allocator doesn’t change the content of freed blocks, such bugs can go unnoticed, making the program “accidentally depend on freeing memory not immediately overwriting it”.
You only mean the former, not both. The only reason for the existence of the giant rats' nest of complexity that is C++ move semantics is eliminating this particular mistake.
char const* inputFilename = std::string("path/to/file").c_str();
call_to_C_API(inputFilename);
Boom. You can typically access `inputFilename` until the next allocation on the stack, but it will almost always go bad quickly. This isn't allocation on the heap, which usually requires another level of indirection, but you get the idea.Isn't it allocating in the stack due to the "small string optimization"? Making string contents longer (or using a C++ standard library which doesn't have a "small string optimization" in the first place) would be enough to have it be allocated in the heap instead.
Changing it to:
call_to_C_API(std::string("path/to/file").c_str());
Makes it valid because the std::string object will live until the semicolon.Modern processors zero things very, very fast. This seems like a defensible approach.
IIRC, Zero Page Allocator's priority was so low that it was even lower than the lowest priority available to Win32 API's.
That way there is no 'dead' data to be probed for.
Soon: programs implement their own allocators and memory pools and stop using the libc malloc, and thus stop benefiting from all the safety valves added to the libc malloc (including TFA), all because of the rumor that the libc malloc is slow (when it likely isn't).
Actually, that happened a couple decades ago.
(I can count the times I did either of these on one hand, but of course this depends a lot on the code your working on).
>Memory Allocation
>Known Issues
>The system memory allocator free operation zeroes out all deallocated blocks in macOS 13 beta or later. Invalid accesses to free memory might result in new crashes or corruption, including NULL-pointer dereferences and non-zero memory being returned from calloc. (97449075)
https://developer.apple.com/documentation/macos-release-note...
edit: I guess this is taking advantage of use-after-free being undefined behavior.
It also has a better interface than malloc() because it can check for overflow evaluating `size*count`, though it'd be even better if C had first-class types and you could pass that in.
> Hopefully you aren’t relying on any abandoned software.
Every major Android update, I get scared. I use an alarm clock (Gentle Alarm) that has far more features than all others, but it’s been long abandoned (as in: removed from the play store, all websites dead. This was a paid app). Android 10 or 11 already broke it, but someone on reddit (where all 4 or so users of the app met :D) found a manifest change and allowing "drawing over other apps" to fix it.
Now it works on 12, but after I already spent hours asking on HN and Reddit for a maintained replacement and testing those, I hope this continues, because with how lacking all others are I’d rather learn Android development and program my own replacement than using something else.
edit: Hah! The thread [0] with the fixed APK is still getting comments from people who found it every few months :D
[0]: https://old.reddit.com/r/androidapps/comments/i01xh3/gentle_...
This is why no one ever pays more than a couple bucks for a mobile app. That would be a terrible investment, as the app could break at any time!
Similarly, this is why mobile will never be a good platform for traditional single-player video games.
> Since this app hasn’t been updated within the last three years [...] it has been removed from the App Store
I also greatly dislike how iOS takes the backwards approach of offloading backward compatibility as a yearly support burden multiplied across every developer - rather than maintaining it in the OS.
Platforms are supposed to absorb developer pain for multiplicative benefit - not the other way around.
Among modern operating systems, iOS is probably the worst in terms of backwards compatibility but it’s also one of the most pleasant to develop for — UIKit/Cocoa is unrivaled in terms of both breadth and depth. One can easily build a top class iOS app with few or no third party dependencies, which is not the case on most other platforms. I don’t think that’s a coincidence… if the iOS dev team were encumbered with maintaining backwards compatibility for upwards of a decade it would be much more difficult to build such a complete and polished toolkit.
I wouldn't even be opposed to older apps running in some sort of seamless-to-the-user virtual machine.
It blows my goddamn mind.
It's not as good as Windows' back compat, no. But then again nothing is, as that's the only platform concerned with preserving not just API compatibility but bug compatibility.
Every version has random will-be-breaking changes that get held off if your app was compiled before said version came out, but there's only a release or two before the change starts to ignore your compiled version (for example, when a massive permissions change comes down the pipe, any app that's compiled against the newest SDK breaks immediately, and apps that were compiled against older ones just get to break next release)
And just like any mobile platform, apps heavily rely on the OS to provide UI widgets and layout. So even design language changes can (and often do) break the UI.
-
What you're probably seeing is what developers are forced to work around: the fact that everyone is stuck targeting ancient versions of Android because Android users have just accepted their devices never getting OS upgrades.
Android Studio will still default to targeting a minimum SDK version of API 25. Android 5.0. From November 12 2014.
The situation is so bad that Google removed it from their dashboard (https://developer.android.com/about/dashboards) and buried it in the IDE: https://9to5google.com/2022/05/20/android-2022-distribution-...
Nearly a third of Android devices are on a version of Android >5 years old... just imagine if all your iOS apps had to target iOS 11 as a minimum feature set, and XCode defaulted to supporting iOS 8 for all new projects
When the apps can't rely on any newer features, it creates the illusion of backwards compatibility.
And the rest of your rant is about developers updating their app needing to track changes. That's an unrelated discussion. We're talking about backwards compatibility for apps that don't update. Meaning compile SDK & target API aren't changing. Android keeps those working quite well.
> So even design language changes can (and often do) break the UI.
Nonsense. The platform widgets are governed by the theme which almost never changes. Old apps that don't get updated will still be depending on Theme.Holo which is still there, still unchanging.
And most apps are primarily not using platform widgets anyway, they're using Jetpack instead. Things like RecyclerView, MotionLayout, ConstraintLayout, CardView, etc.. are not in the platform, they're in jetpack. Which is a static library bundled into the APK. And therefore obviously not changing with an OS update
> Android Studio defaults to targeting the latest API level
Whoops, you confused compileSdkVersion, targetSdkVersion, and minimumSdkVersion (I even mention targetSdkVersion separately as "compiled against")
You (wrongly) thought I was talking about targetSdkVersion (which often doesn't match compileSdkVersion on new releases because... targetSdkVersion lets you duck those breaking changes! That's why Google usually gives about a year before any given compileSdkVersion requirement [latest release] is graduated to a targetSdkVersion requirement).
I mean, read what I wrote again: "targeting a minimum SDK version of API 25".
Min SDK is still defaulted to selecting Lollipop in AS Dolphin which hit full release just this month, and when you ask AS for help it specifically highlights API 25. Try it yourself :)
-
> And the rest of your rant is about developers updating their app needing to track changes. That's an unrelated discussion. We're talking about backwards compatibility for apps that don't update. Meaning compile SDK & target API aren't changing. Android keeps those working quite well.
You didn't understand the conversation so you failed to follow the connection. I'm saying that Android doesn't do backwards compatibility well, but it doesn't need to since everyone is actively going out of their way to support old versions.
That's why any moderately long lived Android codebase will be full of:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.XXX) useOldPermission/Flag/Type else useNewPermission/Flag/Type
> Nonsense. The platform widgets are governed by the theme which almost never changes. Old apps that don't get updated will still be depending on Theme.Holo which is still there, still unchanging.
Forget OS version, the theme changes per device manufacturer! There've been cases of manufacturers redefining constants like android.R.color.black to be something completely different than black! That's why you can't rely on android.R for anything
> And most apps are primarily not using platform widgets anyway, they're using Jetpack instead. Things like RecyclerView, MotionLayout, ConstraintLayout, CardView, etc.. are not in the platform, they're in jetpack.
You failed to realize that the part of Jetpack they're using the most is a library literally called AppCompat. You know why it's called that?
It try (and sometimes fail) to wrap dozens of versions worth of incompatibilities, bugs, breaking changes, and missing implementations in the user space (app)!
It's a literal ode to my entire point, that Android *the OS* has never done backwards compatibility well, so developers *in userspace* end up putting a *ton* off work into glossing it over!
Imagine if on iOS you couldn't just make a UITextView, you had to use a wrapper around a UITextView because iOS 8 was still a valid target for many apps and any new feature for the UITextView in the OS had to be simultaneously back ported to iOS 8...
Just the other day I observed a case where the Jetpack Cardview worked on API 32 but broke on API 25 because on API 32 the library falls back to the built-in from the OS... Well it turns out the AppCompat implementation just randomly breaks if the radius of a corner is greater than View.height / 2. The OS implementation handles that gracefully.
That's why any half-serious development team ends up needing to run their apps on all those versions of Android (or just unwittingly lives the life of one of those randomly poorly behaving apps in the wild)
-
Like if you just slow your roll and read what I said, it shouldn't be controversial to any serious Android developer.
You said it yourself... Android has reached the point where basic UI development is literally built on a ball of strategies to work around the atrocious backwards compatibility situation via AppCompat and other libraries in Jetpack... do you realize that's not normal or indicative of an OS with good backwards compatibility? It's an utterly bizarre corner that Google backed themselves into and are aching for outs on
Which is not relevant to the discussion. The discussion is about apps that aren't updated. Android doesn't break those. Developers targeting previous releases is a wholly unrelated issue.
> AppCompat. You know why it's called that?
Because it provides compatibility for new APIs on older platforms. You seem to be completely lost on what backwards compatibile means.
It means old binaries/APIs work on new releases. Which it does on Android quite well. It does not mean that new APIs magically work on old releases. Which is the problem jetpack/appcompat is targeting, and is the entirety of your rant.
> android.R.color.black to be something completely different than black! That's why you can't rely on android.R for anything
And yet everyone does rely on android.R, because those color redefine issues are vanishingly rare.
There's also entire CTS suites that verify oems don't modify theme.holo and theme.material. which is why everyone inherits from those, and don't have problems with it
No, it is the discussion. If you rely on the OS and lean into new features, many updates will break your app, and in fact many devices on the same OS version will break it!
But there's a massive reluctance to rely on anything but the lowest common denominator of functionality in your apps, so there is outwardly an impression of backwards compatibility.
Seriously, you're talking about backwards compatibility when Android can barely do sideways compatibility, the mind boggles.
> And yet everyone does rely on android.R, because those color redefine issues are vanishingly rare.
Well I guess I'm referring to a certain level of experience... so just call it a code smell.
You get Lint warnings for not fully qualifying any use of it since it's usually not what you want.
Those of us who've needed to support enough apps realize it's really not that rare for random android.R overrides to exist in things like animations, and especially if you have a global footprint. So we just take 5 seconds to define ourpackage.R.color.black or ourpackage.R.anim.fade_in
> There's also entire CTS suites that verify oems don't modify theme.holo and theme.material. which is why everyone inherits from those, and don't have problems with it
No... they inherit from AppCompat wrappers on those because there are many subtle breaking changes between versions.
-
You're clearly not making the logical connection conveyed here, and I don't think there's anything wrong with accepting that.
No one will completely understand or agree with everything they read, but clearly others are able to make the link, and I think I've gone out of my way enough explaining the link, but you're completely stuck at the first step convinced that someone who's dealt with backwards compatibility for a very long time doesn't know what it is, instead of opening your mind to comprehending the bigger picture.
So I'll leave it at that :)
Most things that actually break on Android break for good reason, in my opinion. I consider giving apps the ability to draw overlays without any kind of permission in a supposedly sandboxed environment a design flaw, for example. I'm more annoyed by their restrictions on dynamic loading but even that still seems to work fine if you avoid the Play Store and set your API targets to a version that triggers the backwards compatibility system.
The recent change to the way uploading to Google Play works (where you have Google sign your apps instead doing it yourself) is the worst change in years, but that's technically a Google change, not an Android change.
Almost all API changes necessary to run old apps are minute. The permission overhaul for Android 6 (interactive permissions for many permissions that were previously granted install-time) requires a few more lines of code to show an error if the user denies a critical permission but it doesn't require a full rewrite.
The consequence is that app devs with some minimal time on their hands can update their apps to work on modern devices quite easily. Only apps that are thrown over the wall and abandoned or created by defunct companies are now broken.
Luckily, decompiling most Android apps is quite trivial. If the app is indeed completely defunct with the company disappearing, one might relatively safely maintain a decompiled fork of the abandonware to make it work on modern platforms.
It's a pain, but not a dissimilar amount of pain to searching working on NoCD cracks for games that require DRM broken on Windows 10. Enthusiasts do it, quite regularly as it turns out!
This is also why I'm in favour of laws about source control for abandonware; if a company doesn't exist anymore and people still use their software, the source code should be opened so customers aren't screwed over. Source escrow isn't unheard of in B2B applications and I don't see why it can't be done for consumer apps.
Also, right away any sort of law like that would become incredibly nuanced. I have a pacemaker and there's an optional BLE app for it. Security through obscurity is lame, but I'd rather that app's source code not be dumped on the internet if the pacemaker company stops supporting it. The source code wouldn't keep the app going because it is dependent on the cloud, and would only be of value to folk trying to reverse engineer the diagnostic protocol. Personally that sounds like a lot of fun, but it's also hardwired into my heart...
Source code for such products shouldn't be a problem because devices like these should definitely come with the necessary signature checks out of the box. Such key material shouldn't be released for anything important to one's health (medical devices, emergency devices, etc.). If you're at risk of having a heart attack when someone beats some kind of app code obfuscation then you're in for a bad time.
The cloud won't save you either. When the company disappears, their domain name expires eventually. Someone malicious could record and analyse API traffic and buy the domain to set up their own evil API server.
There are so many attack vectors for such a setup that I'm worried about how tech-ingrained such crucial devices are.
FWIW, Gentle Alarm doesn’t do that :D
It’s not quite clear why giving the permission helps, but without it, the alarm doesn’t go off when the screen is off (it still does if the screen is on and the app in the background).
I can easily see the dev calling into the API to initialise the alarm dialogue before actually playing the sound (after all, who wants an alarm they can't turn off?) and the API call that used to go through without issue now throwing an exception, killing the wakeup process dead in its tracks. I haven't reversed the APK, or course, but it's a very likely scenario.
I don't think this is how the API is intended to be used, but you can find plenty of stackoverflow answers like https://stackoverflow.com/questions/35327328/android-overlay... that (ab)use the window API. This one: https://stackoverflow.com/questions/46860993/window-over-oth... shows the API change, throwing exceptions on API level 26 (Android 8) that didn't get thrown before.
On modern devices, these old workarounds are usually no longer necessary. There's a separate API which is easier to use for interacting with the lock screen now and the permissions to take over the lock screen have been made harsher. For a maintained app this would mean fewer compatibility hacks, less weird lingering visual artifacts in edge cases and a better alarm. Sadly, with the dev ending their operation, the alarm is probably stuck calling old APIs with limited support now that Android security is tightening. Such a change broke clipboard sync in KDE Connect, for example, except it didn't leave a compatibility workaround for old apps.
I'm in favour of better privacy and security but restricting previously unrestricted APIs is a painful process. I've gone so far as to root my phone to provide the necessary permissions but I don't think I'll be able to do that forever...
This is made easier because apps are distributed in bytecode form, and compiled to machine code on installation. The bytecode is retained in the .apk file, IIUC.
> I need it to allow me to set a time of X minutes during which it gets louder until it reaches the pre-set volume.
> pre-alarm: It’s an alarm that goes off X minutes before your main alarm, at a very low volume, with its own settings of volume, time to reach full volume, media to play, etc.
From my Reddit thread [0] back then. It was the upgrade to 10 that did it.
[0]: https://www.reddit.com/r/androidapps/comments/jvbsuz/looking...
I feel you and that's why we need more open source software.
My favorite Windows apps are the same (MediaMonkey, EmEditor, and DirectoryOpus), paid apps with tons of features, more than I need, but also all that I need.
In addition, I’m already trying to get rid of Alexa, I really don’t want to add another voice assistant.
There is no behavior that a program could legally rely on when reading freed memory. The assumption behind malloc/free is that freed allocations aren't used by the program anymore, so correct programs don't rely on reading freed memory at all and the allocator implementation is then free to do anything at all with the freed memory, it just turns out that the simplest behavior on the allocator side is to do nothing at all and just leave the freed memory as-is.
The only thing this does when speaking of programs reading freed memory is that it breaks existing incorrect programs that just happened to work because of an implementation detail of the allocator, and then later someone will write another incorrect program that just happens to work because the allocator now zeroes out freed memory. To actually stop the problem of programs reading freed memory, the OS side would have to enforce the use of memory-safe languages which by design cannot access unused memory.
The memory could get allocated again before it is re-used, and whatever is put in it might be less trappy than a random value. And, of course, a random value might not trap.
Better is not to do things that result in referring to dead memory: easy in modern C++, apparently difficult in C.
I doubt this actually will be a problem for 99% of all apps, the ‘but what about this essential software from 50 years ago’ problem does not exist on Apple platforms because they routinely deprecate and then drop obsolete software without mercy.
The only real problem I can imagine occurring is that this change uncovers dangerous security vulnerabilities that are a crash (so a denial of service) on those platforms but an exploit on others. But that’s a problem for those other platforms to deal with.
Nowadays it takes work to get more than 10x, but hardly any to get there.
If the system allocator gets slower people might be more inclined to switch to something custom or use locally recycled allocations which may make use-after-free bugs even harder to find.
I'm sympathetic but I wonder if it would be more sensible as an option that's on by default instead.
Though some chips optimize zeroing memory. For example Intel does, perhaps Apple does as well?
https://travisdowns.github.io/blog/2020/05/13/intel-zero-opt...
Is there a reason you don’t want this on by default?
It's only on-everywhere because Apple doesn't do backwards compatible. Update or get kicked out is the expectation.
E.g. a malicious app mallocs a large buffer and just reads the memory looking for anything interesting? AND as you pointed out a non-malicious app that doesn't handle input.
> It's only on-everywhere because Apple doesn't do backwards compatible.
If this causes issue in an app then doesn't it mean that app has a possible vulnerability? So, it can be argued that breaking an app is increasing the security of the ecosystem. And yes, I understand the other side too of having an obscure app that you depend upon breaking because it is no longer supported. That's also the type of app that doesn't have a high probability of being exploited.
No, the OS zeros pages before giving them to a new process, otherwise you'd have all kinds of information leaks across security boundaries.
You only see dirty memory within the same process when you malloc and then free and malloc again and get a page from the free list within the process. Increasingly allocators are zeroing those too though to reduce the attack surface from malicious inputs (which is what changed in iOS).
Only if the app had any untrusted inputs in the first place. If the app doesn't ever touch the network or only connects to its own servers then no, it's not really improving security.
[1]: Or even 0, as in Big Sur, where it's provided inside the runtime linker(/loader), instead of being a separate file.
SOME attacks are going to be harder, others are going to be easier(at least that's what a Project Zero researcher thinks)
The technique is can also be generalized beyond just triggering malloc() failures under specified conditions.
This makes a lot more performance sense for those languages.
It is mentioned that if you write to freed memory, that could lead to later callocs not returning zeroed memory, which shows they are relying on this.
EDIT: We seem to only get the zero behavior ?? < 100 < ZEROMEM <= 1008. So malloc 1009 isn't zeroing for us. And some value below 100 isn't freeing and just poisons
When you deallocate small amounts of memory, the underlying C library allocator will keep the deallocated memory around and not return it to the OS in case you need a similar sized piece of memory soon.
It is very rare for memory ever to be freed to the OS before process exit. Malloc never does.
And, zeroing the memory, if code later uses it like a pointer, that will be null and the use will trap.
[1]: <https://github.com/bminor/glibc/blob/a364a3a7090b82ddd30e920...>
Unmapping trashes page-map caches of other threads in the process. That involves inter-core interrupts. Not pretty.
#include <stdlib.h>
int main() {
for (size_t i = 0; i < 4096*4096; i++) {
void* p = malloc(i * 4096);
free(p);
}
}
Running strace on it gives me a whole spam of logs like: mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0e7d46f000
munmap(0x7f0e7d46f000, 139264) = 0
mmap(NULL, 143360, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0e7d46e000
munmap(0x7f0e7d46e000, 143360) = 0
mmap(NULL, 147456, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0e7d46d000
munmap(0x7f0e7d46d000, 147456) = 0
mmap(NULL, 151552, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0e7d46c000
munmap(0x7f0e7d46c000, 151552) = 0
mmap(NULL, 155648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0e7d46b000
munmap(0x7f0e7d46b000, 155648) = 0
mmap(NULL, 159744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0e7d46a000
munmap(0x7f0e7d46a000, 159744) = 0
That was run under a standard Debian 11 installation.Now I need to find the nonstandard flags to set to turn it off.
Most CPUs provide memory permissions with granularity of pages, which are usually 4 KB in size. The OS manages these by modifying a "page table" which the CPU uses. Changing the page table is not free, so the userland allocator (malloc/free) will request large blocks of memory from the OS (with mmap) (or sbrk, if you're a dinosaur) and then divide them up into smaller blocks for your program. They typically remain accessible until the entire original block is freed.
If you allocate a large enough block of memory, it will typically just be backed by one region of pages made with mmap. Then, when you free it, the allocator will munmap it. You can test this out by allocating a large block of memory, freeing it, and then dereferencing it.
Your system might do this for 1 MiB allocations, for example... change the allocation size and see whether it still crashes.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
volatile int *ptr = malloc(1024 * 1024);
puts("*ptr = 1");
*ptr = 1;
free(ptr);
puts("*ptr = 2");
*ptr = 2;
}They're 64KB on RHEL/CentOS 8 on 64-bit ARM, see for instance https://git.centos.org/rpms/kernel/blob/c8/f/SOURCES/kernel-... which is AFAIK the kernel configuration for CentOS 8 (search for CONFIG_ARM64_64K_PAGES=y).
Some of the more common malloc implementations out there are dlmalloc, jemalloc, and tcmalloc. Here is where they call munmap.
https://github.com/ennorehling/dlmalloc/blob/71296436f979a35...
https://github.com/jemalloc/jemalloc/blob/c0c9783ec9289e6d1d...
Note that not all allocators work this way, you can see that tcmalloc will instead MADV_DONTNEED. (You can find munmap in tcmalloc, but it's not being used to free memory.)
https://github.com/google/tcmalloc/blob/48808681a9f38e974b49...
Some CRT's just go get their own block of memory then malloc out of that. Some use the OS extensively to minimize that sort of thing. It just depends.
Would that be useful here even in a computer context?