Kernel kills my dylib even though it's properly codesigned

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:

  1. loader.cpp:
#include <cstdio>
#include <iostream>
#include <dlfcn.h>

int main() {
    void* handle = dlopen("libtest.dylib", RTLD_NOW);
    perror("dlopen");
    return handle == nullptr;
}
  1. test1.cpp:
#include <cstdio>
#include <iostream>

__attribute__((constructor)) void initfunc() {
    std::cout << "hello from dylib1\n";
}
  1. test2.cpp:
#include <cstdio>
#include <iostream>

__attribute__((constructor)) void initfunc() {
    std::cout << "hello from dylib2\n";
}
  1. 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
  1. 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%):

  1. Run Make or CMake to compile the source files,

  2. Copy libtest1.dylib to libtest.dylib:

    $ cp libtest1.dylib libtest.dylib
    
  3. Run ./loader

  4. Observe proper runtime without errors:

    $ ./loader                  
    hello from dylib1
    dlopen: Undefined error: 0
    
  5. Copy libtest2.dylib to libtest.dylib:

    $ cp libtest2.dylib libtest.dylib
    
  6. Run ./loader

  7. Observe crash:

    $ ./loader                        
    [1]    96122 killed     ./loader
    
  8. Remove libtest.dylib

  9. Repeat step 5: Copy libtest2.dylib to libtest.dylib:

    $ cp libtest2.dylib libtest.dylib
    
  10. 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
Answered by DTS Engineer in 733154022

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?

See Updating Mac Software.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Can't edit the post, so I'll just add more info here:

The example in the post has been checked on macOS M1 12.2 and 12.6, and it's triggering there.

I've also checked on 10.15.7 (19H1217) on AMD64, and it's NOT active there. There's no crash/kill on this OS version.

Accepted Answer

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?

See Updating Mac Software.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Kernel kills my dylib even though it's properly codesigned
 
 
Q