macOS 11: copies of dynamic libraries are no longer present on the file system
twitter.com
twitter.com
Edit: Since this comment is currently at the top, here's a better link to the source: https://developer.apple.com/documentation/macos-release-note... (using scroll-to-text fragment[1] since there's unfortunately no usable anchor to the specific entry; not supported in non-Chromium browsers at the moment, in that case search for "built-in dynamic linker cache").
Not supported in any-browser-that-follows-the-spec, really, considering scroll-to-text is a google made, unilaterally implemented, nonstandard extension (https://wicg.github.io/scroll-to-text-fragment/)
Anyway, I was expecting a few shitstorms to start happening over macOS 11. Apple not announcing any major deprecations (edit: or removals) for this transition - even OpenGL/CL is still there, depecrated - was suspicious.
Say a 'release' turns out to have a discrepancy between documentation and reality, that's when the problems start to mount.
It wouldn’t surprise me if there’s no way to get your dynamic library there anymore, as this explicitly says it’s of the system provided ones.
dlopen() will look for the dylib first (as today), and if the file doesn't exist, it'll look in the shared cache.
So the only people this actually impacts are those who stat() system-provided dylibs before calling dlopen(). They should just skip the stat(), and go straight to calling dlopen().
Certainly not worthy of the end-of-the-world vibes in the twitter thread...
@dang - I flagged this for the editorialized title, in my view it's worth updating this to something like "Restrictions on reading dynamic libraries in macOS 11".
But `stat`ing it used to be fine on a Mac. And `stat`ing it is fine on other OSs. Why force software to change for no apparent reason just to appease the arbitrary whims of Apple? (And if it's not an arbitrary whim, you'd think they'd at least hint at the upsides of this, no?)
My guess as to the reason: I would guess the new way is faster, more secure, or takes less memory. Leaving an unused copy of binaries around would be wasting disk space (and yes, disks are enormous nowadays, but SSDs aren’t)
It may cache data from Apple’s servers, but calling it a cache sounds incorrect, though.
Why assume there's no reason?
Note that the dyld shared cache itself has existed for a long time; the only change here is removing the original copy of the dylibs to save disk space.
Like all race condition bugs it is probabilistic, but it can be won. By contrast dlopen either succeeds or does not, and once you have the handle to the library you are good.
Actually windows also works this way, and has done since wow I think vista if not XP? There's a knowndlls registry key. Large parts of the windows api are contained in a few DLLs; these are loaded on system startup. When resolving shared libraries if one of these names is requested the normal DLL loading procedure of checking paths isn't even followed - it just links in the already loaded DLL from the cache.
It actually wasn't designed as a security feature, but for speed. No matter what the OS does there's little need to continually load these DLLs from disk when we know they are already loaded to even display the desktop.
Of course on windows you can still do the windows equivalent of stat'ing these DLLs but nobody ever does.
FWIW, this optimization is unnecessary on modern Unix systems with a unified buffer cache because libraries loaded from disk are already CoW'd into the user process, without the need for special semantics. It's a pertinent point, nonetheless, but I'm guessing Apple made the switch in macOS for other reasons, perhaps related to disk space, its less than ideal ASLR scheme for Mach-O, or something related to code signing and sandboxing.
I second the TOCTOU concern. It's a bad code smell and a poor habit to stat and then open a file, period. (Likewise for the more general pattern.) There are reasons to do it (some better than others), but they're exceptional. You should never be eager to do it, and when you do you should rule out all possible security exploits or bugs, which is usually more work than abstaining from the superfluous hack. Breaking such code patterns is worth the cost in backward compatibility, IMO, for all the exploits and usability headaches this coding pattern leads to.
Linkers just need to somehow find the binary dependencies across the link path/staging.
Performance.
I don't see the big deal as long as app local copies of e.g. curl can still be used
C:\Windows\System32 is where the 64 bit libraries live
C:\Windows\SysWOW64 is where the 32 bit libraries live
This is silly enough that it deserves a good-natured elbow-jab every now and then.But "Windows on Win64" is WOW64, i.e., Win32 on Win64. Because by then "Windows" meant Win32.
so "SysWow64" is the "System directory for WOW64".
I've been on Windows too long because it all seems perfectly logical to me.
The paths can be either absolute (for system libs) or relative (useful for distributing Bundled apps).
The cool thing is that you can see those hardcoded paths using otool -L and you can modify them using install_name_tool. Useful for debugging misbehaving closed-source tools.
Most comments here on HN seem to misunderstand what this is.
I often dig at shared libraries using tools like nm to see what functions are where.
$ nm /System/Library/dyld/dyld_shared_cache_x86_64h
/Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm: error: /System/Library/dyld/dyld_shared_cache_x86_64h: Invalid argument.
But yes, there are tools to pick apart a shared cache, some which work better than others.But the platform does seem to have been in a multi-year process of technical decline, and much of that does speak to a "we know better than you, and everybody else" attitude.
But dlopen() implies code execution all the time, and stat() does not.
A good way to close this race condition, by the way, is to have a dlopen-by-fd API [FreeBSD has fdlopen], and a single call to open(2) resolves the race condition inherent in referencing the filename multiple times.
But to your larger point... It seems there is a crowd that will kneejerk dismiss any criticism as "it's for security". And any implementation bugs (such as introducing massive delays when executing programmatically-generated shell scripts because they need to be hashed and sent to a server, as was an HN headline a while ago) are also justified. I think there needs to be greater skepticism than this about how secure these efforts really makes the system, or if they are just throwing buggy stuff at a wall and applying post hoc justifications.
All 10 of them?
If in your personal life you are needlessly a jerk to a small number of people... Do you turn around and say "yeah, 10 people. So what?" I suppose many people do.
Apple didn't do it to be jerks, and didn't do it needlessly (except in the sense "they could just leave it as is").
Jerkiness is subjective, but in my opinion there is a question of respect for the user, developer, or fellow human involved in the area of compatibility, where it is kind of a jerk move to fail to consider that you are breaking people, or someone might narcissistically expect the universe unconditionally to adapt to their whim, making them jump through hoops and live with the consequences of their questionable ideas. Apple does not have some of those values down well.
autoconf developers and users. The whole lib probe ecosystem might need adjustment.
On the first look it's not that different from how this works now. However, I'm thinking of the next step where the 'system cache' is not even local to the PC, like in a case of a netboot the whole or core set would get downloaded from the remote server be it Apple's or your friendly custom provider/employer.
MacBook Pro VT. It's a stretch, of course...
This thing can be taken apart, but it’s a little tricky, since it’s pre-linked. I wrote something years ago to do it, not perfect, but enough to get symbols out. (I was trying to annotate iOS stack traces.) I don’t recall if I got it working well enough to open the libraries up in hopper and poke around.
Does this affect homebrew?
What would you prefer they use? Itanium? RISC-V?
Apple could make a great leap forward by adopting the Mill, an ISA so novel that it hasn't even been released yet!
Makes me think of the Tanenbaum quote from 1992: "Of course 5 years from now that will be different, but 5 years from now everyone will be running free GNU on their 200 MIPS, 64M SPARCstation-5".
Funny how the modern world runs on the descendants of an overgrown calculator chip.
Someday it may sit in some kind of "protected enclave", mapped into your process with "execute-only" permission for code, and maybe graded protections for its data as well, so you can't debug, trace, instrument, reverse engineer or otherwise get to really know the system libraries to see what they are doing and learn from them.
The trend has been that way for a while. For the moment, that kind of isolation is done at process boundaries, on devices that have enclaves.
There is no technical reason why that kind of isolation cannot extend inside processes, for a company that designs its own CPUs. For the same kind of "black boxes" as we already have inside mobile devices, but with faster API calls and data exchange than doing it across processes boundaries.
Filesystems are awful databases and I believe everyone who was a programmer for some time realizes this. The lack of ACID being at the top.
The sooner we move to specialized APIs for, well, everything, the better off we're going to be.
---
As one recent example, the PS 5 will just directly load 3D artefacts from the SSD storage to the GPU when the corresponding API is called. This gives Sony peace of mind that people won't be coming up with 100+ ways of doing it by themselves which Sony will then have to support for the entirety of the PS 5 lifecycle.
We the programmers have demonstrated to ourselves, time and again, that too much freedom can actually be a bad thing.
It's okay to move away from the "everything is a file" philosophy. If anything, I'd love to see a Linux-like OS that utilizes sqlite3 for, say, the entirety of the current `/etc` directory and uses sqlite3's backup/changeset APIs to do incremental and point-in-time backups. Sure it's little less transparent but you can't ever capture an inconsistent snapshot. That has to be an improvement, right?
---
EDIT: I suppose people can't find a link between my comment and the topic but my point was that moving to dedicated APIs -- and not relying on the filesystem -- for various services (like loading a dynamic library) is a good thing because the "everything is a file" paradigm isn't as bulletproof as it's made out to be (also because "stat" + "dlopen" one after another are race-condition prone as others pointed out).
I'd love to hear the downvoters elaborate their reasoning.
Lots (I wouldn't be surprised if most) osx devs use case sensitive filesystems, but the default in case-insensitive, and so dlopen(path) fails if you have (say) "/system/..." instead of "/System/..."
Historically the dyld_shared_cache has been a pre-bound copy of most system libraries that is mapped into memory once and then shared across all processes. It was updated automatically by the system at various times, and if a given system library was determined to be newer then the dynamic linker would simply load it from disk. The result of this was that for most system libraries you ended up with two copies on disk: one at the normal location, and one within the shared cache. Now only the latter copy exists, and Apple ships it directly rather than creating it on each machine from the copy of the system libraries that they ship.
For many years, macOS has supported being a cross compilation environment for multiple platforms and multiple OS releases.
This means that include files and libraries for the target OS and platform are not stored at the "host filesystem" paths that were used in the 1960's, 1970's and 1980's... (an embarrassingly long list). Include files and references to libraries are available in the SDKs. One should use the SDK path as a prefix for the include and library paths - something that most GNU tools, and other open source projects have supported for a very long time, but sadly a little inconsistently. There are runtime tools and recipes that can be used to get these paths, including for "myself".
All compilation on macOS should be considered as cross compilation -- because macOS simultaneously supports multiple architectures and OS versions.
This really is a good abstracted model for all compilation.
This has benefits! Install time and disk space usage are reduced for most customers and developers. Developers on x86_64 do not need the aarch64 content in /System, /usr to compile. Install the SDKs for the targets you wish to compile for.
Does this break pkg-config?
What about fishing for dylibs in .bundle directories that may be installed in multiple locations?
Apple sticking with Intel and now reverting to a terrible proprietary cpu architecture, locking software down more and more, releasing pro versions with inadequate options and keyboard all acts to ensure they lose their status as top developer environment
Before they dropped powerpc for intel, no one would have considered using a mac for dev work.
It's clear they don't understand the developer market at all or are only interested in Mobile because the revenue is insane.
They had a good ten years as the best dev environment but as they say "The King is dead, long live the King".
Are you referring to the ARM instruction set generally being more open for custom instructions? Going by the number of licensees and producers of ARM silicon, I'd say ARM is less proprietary compared to x86 which is basically just produced by Intel, AMD, and a Chinese licensor AFAIK.
ARM is more open than x86. In fact, you could start your own ARM chip design company today. Which is something you cannot do with x86 at all, even if your were to do a complete takeover of AMD or Intel.
> Before they dropped powerpc for intel, no one would have considered using a mac for dev work.
DHH wrote Ruby on Rails on a Powerbook. Microsoft did all the development of the original XBox on PowerPC Macs. A lot of developers used macs before Intel, and they will use them after Intel.
> They had a good ten years as the best dev environment but as they say "The King is dead, long live the King".
Given the Frankenstein horror show that is Windows with WSL2 and the terminal lack of a polished working GUI in Linux, Mac is still the best dev environment unless you are doing some platform specific work.
> Given the Frankenstein horror show that is Windows with WSL2
Here I will politely disagree. WSL2 is far from a monster. It is a quite nifty hack, and fast at that.
I'm normally working on a Lknux machine but I've used Windows with WSL2 day out and day in now and I am only aware of it twice or maybe three times a day, and even then it is not a horror show - quite the contrary - it is close to the definition of seamless.
> and the terminal lack of a polished working GUI in Linux,
To each their own. I won't get into enumerating everything I missed while I used Mac but I can say I tried very hard to make it work as some parts of the MacBook experience was really great.
> Mac is still the best dev environment unless you are doing some platform specific work.
As thousands of devs who are free to choose their own laptop and yet doesn't choose Mac can tell: it is a bit more complicated.
I'd appreciate if we could stop propagating this myth now.
Mac is different. Some enjoy it. Others cannot stand it.
Ads everywhere, surprise "you don't have a choice" updates, and just the other day after an update it locked the screen to force me through a slideshow that tried repeatedly to coerce me into using Edge ("let's import your bookmarks / contacts / whatever" and the "please not now" button was de-emphasized and kept changing names and moving between slides).
This is actually a good point.
At Microsoft guys around here: Please tell your folks that nothing says quality like when your paid business OS tries to upsell you like a really cheesy salesman /s
Err, there are more cpus with ARM than x86 by an order of magnitude...
It's also less proprietary...
>Before they dropped powerpc for intel, no one would have considered using a mac for dev work.
Huh? Where did you get that from? OS X Macs where quite popular for dev work pre-Intel, popular for web devs, and with good Java support...
Well I mean it only applied to system libraries. And for those you can now just `dlopen` and check for failure, so it can't be about privacy.
> Why are they locking down my device from me?
I mean isn't this what Apple has been doing at least since the turn of the millenium?
Because it belongs to the end users, and it should be as task focused, opaque, and easy to use as an appliance, not something you tinker with.
(glibc and some other core libs like openssl like to detect plugins by probing the paths for libs and only loading them when necessary)
I would have thought that even if Apple did make this change they could trap the fopen or whatever and return something useful based on a heuristic? It wouldn't be the first time that they put exceptions and heuristics in for software compatibility.
Check it... for what?
Are you currently checking files are there before you dlopen them? How do you avoid a race between the stat and the dlopen? What happens if the file is removed between them? Seems broken and you shouldn't be doing this in the first place.
You're opening yourself up to a lot of race conditions otherwise, and this has been the source of many security vulnerabilities
System libraries (depending how Apple defines that)? Unusual.
Does anybody know?
Brew either uses the system libraries (which still work) or compiles its own version (which is not affected by this)
Not to mention they could just backdoor the kernel, and since that's not open-source, good luck detecting that.
How would you know that Apple was loading what was on disk, before this change?
For example, jtool2 does it for iOS, where Apple has already purged system dylib files.
You aren’t allowed to go fishing around the system anymore.
Sure you can. You'll now `stat` around for everything you did before, but now you'll have to `dlopen` the system libraries instead. Surely no privacy win?
Why do you care if the files are on the disk or not though? What are you using that for?
This is a very good change.
Now you got a probing tool with executive side effects, before it was pure read-only. Not everyone will be happy with this idea to execute first, look later. dynamic libs execute init functions at dlopen(). Check the various ld security implications.