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.dylibtolibtest.dylib:$ cp libtest1.dylib libtest.dylib -
Run
./loader -
Observe proper runtime without errors:
$ ./loader hello from dylib1 dlopen: Undefined error: 0 -
Copy
libtest2.dylibtolibtest.dylib:$ cp libtest2.dylib libtest.dylib -
Run
./loader -
Observe crash:
$ ./loader [1] 96122 killed ./loader -
Remove
libtest.dylib -
Repeat step 5: Copy
libtest2.dylibtolibtest.dylib:$ cp libtest2.dylib libtest.dylib -
Run
./loaderand 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