Kernel tells me that my dylib has an invalid digital sign, but for 100% it's a proper digital sign. Is this a bug, or a feature?
Minimal 100% reproducible example:
- loader.cpp:
#include <cstdio>
#include <iostream>
#include <dlfcn.h>
int main() {
void* handle = dlopen("libtest.dylib", RTLD_NOW);
perror("dlopen");
return handle == nullptr;
}
- test1.cpp:
#include <cstdio>
#include <iostream>
__attribute__((constructor)) void initfunc() {
std::cout << "hello from dylib1\n";
}
- test2.cpp:
#include <cstdio>
#include <iostream>
__attribute__((constructor)) void initfunc() {
std::cout << "hello from dylib2\n";
}
- Makefile:
all: libtest1.dylib libtest2.dylib loader
libtest1.dylib: test1.cpp
c++ -shared test1.cpp -o libtest1.dylib
libtest2.dylib: test2.cpp
c++ -shared test2.cpp -o libtest2.dylib
loader: loader.cpp
c++ loader.cpp -o loader
- CMakeLists.txt instead of Makefile:
cmake_minimum_required(VERSION 3.5)
project(crash_test)
add_executable(loader loader.cpp)
add_library(test1 SHARED test1.cpp)
add_library(test2 SHARED test2.cpp)
Store all files into one directory and compile the binaries either by using make
or cmake
.
Reproduction scenario (100%):
-
Run Make or CMake to compile the source files,
-
Copy
libtest1.dylib
tolibtest.dylib
:$ cp libtest1.dylib libtest.dylib
-
Run
./loader
-
Observe proper runtime without errors:
$ ./loader hello from dylib1 dlopen: Undefined error: 0
-
Copy
libtest2.dylib
tolibtest.dylib
:$ cp libtest2.dylib libtest.dylib
-
Run
./loader
-
Observe crash:
$ ./loader [1] 96122 killed ./loader
-
Remove
libtest.dylib
-
Repeat step 5: Copy
libtest2.dylib
tolibtest.dylib
:$ cp libtest2.dylib libtest.dylib
-
Run
./loader
and observe everything is OK:$ ./loader hello from dylib2 dlopen: Undefined error: 0
Is this a bug or a feature?
It seems to me that something is caching codesign information based on file inode. Replacing files seem to retain old inode while storing new data inside previously existing inode. Shouldn't replacing the file invalidate this cache, wherever it is?
Is this documented somewhere? Maybe macOS requires me to always remove dylibs before reusing their names? I couldn't see any documentation about that.
System information
I'm observing this on various macOS machines, on real machines and virtualized using Virtualization.framework. Currently I'm using this macOS version:
$ sw_vers
ProductName: macOS
ProductVersion: 12.6
BuildVersion: 21G115
It seems to me that something is caching codesign information based on file inode. Replacing files seem to retain old inode while storing new data inside previously existing inode. Shouldn't replacing the file invalidate this cache, wherever it is?
Right, but cp
doesn’t replace the file, it overwrites it. Now, you could reasonably argue that the system should detect that (and I’d absolutely agree with you!) but it’s not how it currently works.
Is this documented somewhere?
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"