crashappsec / pseudolink

Pseudo-static linking for MacOS

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Pseudolink: pseudo-static linking on MacOS

Overview

For command-line MacOS apps, allows you to package all your dependencies in one single executable, without users having to install library dependencies.

The problem

If you're building general-purpose Unix command-line tools, and not Mac apps, the OS does not support statically linking external libraries.

That's not a big deal if you're linking to libraries that are available across all macs, but it's a problem if you want to ship any other third party software.

Generally, tools with such dependencies try to get users to install through Homebrew to manage those dependencies. But many users don't want to go through that trouble.

The Pseudolink solution

  1. Your executable uses pseudolink to bundle all your dependencies. All you have to do is generate a single C file that you compile and link to your project. There's no function you have to call, because;
  2. The code generated by pseudolink runs before your main entrypoint. If not already present, it writes your dependencies to /tmp/dyld_lib_files_[uid]/ (inserting the UID of the user).
  3. It sets up the correct value for DYLD_LIBRARY_PATH, if necessary.
  4. If the generated code did any work in steps #2 and #3, it re-executes your program. This is necessary to make the dynamlic loader recognize the path.

Running Pseudolink

Let's say that, for some reason, you need to bundle in OpenSSL3 to your app, which is definitely not installed on Macs by default.

Build the pseudolink command

viega@UpDog pseudolink % make
cc -Wall -O3 -o pseudolink pseudolink.c
viega@UpDog pseudolink %

Run it, passing your libraries.

viega@UpDog pseudolink % ./pseudolink /opt/homebrew/opt/openssl@3/lib/libcrypto.3.dylib /opt/homebrew/opt/openssl@3/lib/libssl.3.dylib
Output bundled dylibs in dyld_decls.c.
Compile and link that file to your program.
viega@UpDog pseudolink %

Incorporate dyld_decls.c into your project

You need this to be statically compiled into your project, or it won't work. But once it is...

You're done!

Things to Note

Each user gets their own private tmp directory. I did not make them application-specific, but if anyone else wants to use this technique I'd be willing to do that.

Similarly, I didn't make it configurable where that directory goes. I'd consider an option in the home directory, even though I'm fond of extra cruft auto-cleaning on reboot.

Additionally, pseudolink will currently overwrite any existing DYLD_LIBRARY_PATH. That was the right choice for what we needed it for, but again, if anyone needs different behavior, reach out to me (or feel free to submit a PR).

Pseudolink doesn't actually bother to do any checking to ensure the content is actually a dylib. There's no advantage to using this to drop other assets, but I guess if you wanted to do that, you could have at it!

There's also no configurability around the symbol names we chose. You can just edit the generated code if you need to.

If your program has other code that runs before the main entry point (i.e., main() in C), you will need to ensure that, if it depends on the bundled code, pseudolink's code runs first.

How it works

The generated C file declares string constants that contain your dylibs. We use the compiler 'constuctor' attribute to have the generated code run before your program's main entry point.

If we don't find the directory and files we expect, we write them out. And, if that directory isn't set in the dynamic loading library path, then we add it (which leads to us quickly self-execing to commit the changes).

Author

John Viega (john@crashoverride.com) Copyright 2023, Crash Override Inc.

License

Pseudolink is made available under the Apache 2.0 license.

About

Pseudo-static linking for MacOS

License:Apache License 2.0


Languages

Language:C 98.3%Language:Makefile 1.7%