How to bundle a .NET app with mono, such that it can be signed, or such that it can run w/o crashing

Please Note: The project is not a Xamarin.Mac project but a .NET 4.5 project, I've seen a lot of similar questions asked here where answers come from people who seem to know a lot of about mono in general. If this is the wrong place to ask this. I will take down this post.

I've been struggling with this for a month now, unable to bundle a .NET exe with mono such that it runs correctly on a target macOS 10.14 after having been codesignged with strict and hardened runtime options. Either working bundle cannot be signed, or I have a non-working bundle that can be signed.

I have a test project on github, but my account is too new here to share it. The app is a simple "hello world" .NET console app with no external dependencies.

I'll repeat here:

I found a related bug on xamarin bugzilla but cannot share it here. I cannot find any other references to this issue on mono github / stack overflow / xamarin community forums

Requirements:
- Mac OS 10.14.6
- Visual Studio for Mac 8.3.9
- XCode 11
- Mono 6.4.0
- A Signing Certificate (self signed used below)

Steps to reproduce issue(s)

Case 1:
1. compile a .NET 4.5 console app in VS with release config, produces testConsoleApp.exe
2. in the project folder execute the below command of mkbundle with --simple flag
- produces testConsoleApp binary which works when run on the command line "./bin/Release/testConsoleApp"

mkbundle -v -o testConsoleApp --deps --simple testConsoleApp.exe --sdk $MONOROOT/Versions/Current
  1. in the project folder execute the signing command below which code signs with --strict -o runtime and entitlements
codesign -f --strict --verbose --entitlements $ENTSFILE -o runtime -s "$CERT" testConsoleApp --timestamp

Expected: signing to be successful

Actual: signing is unsuccessful, console error: "testConsoleApp: main executable failed strict validation"

Case 2:
1. compile a .NET 4.5 console app in VS with release config, produces testConsoleApp.exe
2. in the project folder execute the below command of mkbundle with -L flag and passes mono directory for the path
- produces testConsoleApp binary which seg faults, error code 11, when run on the command line "./bin/Release/testConsoleApp"
- stack trace below, can't link too it...

 mkbundle -v -o testConsoleApp --deps testConsoleApp.exe -L $MONOROOT/Versions/Current/lib/mono/4.5/
  1. in the project folder execute './signTestConsoleApp.sh strict {cert-to-use}', which code signs with --strict -o runtime and entitlements
codesign -f --strict --verbose --entitlements $ENTSFILE -o runtime -s "$CERT" testConsoleApp --timestamp

Expected: signing to be successful

Actual: signing is successful, but the program still seg faults when executed "./bin/Debug/testConsoleApp"

On which platforms did you notice this

[X ] macOS
[ ] Linux
[ ] Windows

Stacktrace

Process: testConsoleApp [71605]
Path: /Users/*/testConsoleApp
Identifier: testConsoleApp
Version: 0
Code Type: X86-64 (Native)
Parent Process: bash [53460]
Responsible: testConsoleApp [71605]
User ID: 490749117

Date/Time: 2019-11-22 11:51:05.126 -0500
OS Version: Mac OS X 10.14.5 (18F132)
Report Version: 12
Anonymous UUID: DF34AE02-2897-021E-660E-FE6E4146A6B8

Sleep/Wake UUID: B3602C3E-8214-4A17-91B8-05C3B13F041F

Time Awake Since Boot: 270000 seconds
Time Since Wake: 8300 seconds

System Integrity Protection: enabled

Crashed Thread: 0 Dispatch queue: com.apple.main-thread

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [71605]

VM Regions Near 0:
-->
__TEXT 000000010f3cb000-000000010f3cc000 [ 4K] r-x/r-x SM=COW /Users/*

Application Specific Information:
dyld2 mode

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 ??? 000000000000000000 0 + 0
1 testConsoleApp 0x000000010f3cbb16 mono_mkbundle_init + 22 (temp.c:158)
2 testConsoleApp 0x000000010f3cbcc8 main + 376 (temp.c:235)
3 libdyld.dylib 0x00007fff7e6883d5 start + 1

Thread 0 crashed with X86 Thread State (64-bit):
rax: 0x00007fcf78005070 rbx: 0x0000000000000000 rcx: 0x0000000000000002 rdx: 0x0000000000000001
rdi: 0x000000010f3cc0d0 rsi: 0x0000000000000002 rbp: 0x00007ffee0834420 rsp: 0x00007ffee0834418
r8: 0x00000000f7800508 r9: 0x00000000fffffeff r10: 0x00007fcf78000000 r11: 0x000000000000000e
r12: 0x0000000000000000 r13: 0x0000000000000000 r14: 0x0000000000000000 r15: 0x0000000000000000
rip: 0x0000000000000000 rfl: 0x0000000000010246 cr2: 0x0000000000000000

Logical CPU: 6
Error Code: 0x00000014
Trap Number: 14

Binary Images:
0x10f3cb000 - 0x10f3cbffb +testConsoleApp (0) <5F5740B6-64AC-319F-B350-DD9F457A1E67> /Users/*/testConsoleApp
0x10f865000 - 0x10fc5dff7 +libmonosgen-2.0.1.dylib (0) /Library/Frameworks/Mono.framework/Versions/6.4.0/lib/libmonosgen-2.0.1.dylib
0x11f40e000 - 0x11f4786ef dyld (655.1.1) /usr/lib/dyld

Answers

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    This seems like more of an issue for mono.

    I haven't used mkbundle in years, but you should unpack the output and confirm:

    • That it generated a valid Info.plist with reasonable values, in the correct location
    • That the folder structure is a valid app bundle

    Another thing is that you are enabling the hardened runtime with:

    -o runtime

    If you really want that, you need to validate that the correct entitlements are included, including JIT, or you will crash exceptionally early in application launch.

    tl;dr; - I'm not sure how well mkbundle supports signing, and very uncertain if it supports hardened runtime - but this is a mono issue with mkbundle.

  • DancingDevDancingDev Member ✭✭

    Thanks Chris,

    I created an issue on the mono github last week. I thought that I might get more eyes here.

    I did forget to include the entitlements used, but the bundle is crashing before being signed, before adding hardened runtime. I'll add the entitlements used to the bottom of this post.

    mkbundle does not create a .app or an "unpackable" output the way Xamarin.mac does. It creates a packed binary of the executable and it's dependencies along with the mono run time. There is no associated Info.plist , maybe this is the issue? Does codesign expect other artifacts to be present along with the object being signed? Can only .app's or .pkg's be signed, not arbitrary binaries?

    When I inspect the output of a built Xamarin.mac .app, I do see a generic binary in Contents/MacOS . But I'm having trouble getting VS configured to do the codesigning, so that I can inspect what actually gets signed when I let VS handle it. Can you help out in providing what artifacts of a Xamarin.mac .app get signed? Maybe the Xamarin.mac project I am using isn't the greatest example as it's wrapping another .NET project, such that within Contents/MonoBundle I have .exe and DLL . I am very curious if VS would sign all of these or not.

    entitlements used:

    com.apple.security.cs.allow-jit
    com.apple.security.cs.allow-unsigned-executable-memory
    com.apple.security.cs.disable-executable-page-protection
    com.apple.security.cs.disable-library-validation
    com.apple.security.cs.allow-dyld-environment-variables

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    Yeah, I honestly have no idea how mkbundle works under the hood.

    I suspect that it may be rather difficult to get a mkbundl'ed app to have the correct structure for hardened runtime, but on the other hand it might be considered a "single file" application and be under different rules.

    My understanding is not full, but I believe there are three buckets of signed things:

    • A real bundle (App or framework)
    • A installer (pkg or disk image, etc)
    • A single file (command line tool, dylib)

    You can look at the msbuild output (Errors Tab -> Build Output) to see what we're doing as part of the build. Roughly however we:

    • Compile your exe with csc
    • Invoke mmp to bundle things:

      • Create a app bundle structure
      • Copy the Info.plist / entitlements into the bundle
      • Copy the assemblies into a resource folder
      • Generate a launcher app in Contents/MacOS
    • Invoke codesign (Apple tool) to sign that bundle

    The mono folks might be able to give you more info, but if you really want a macOS application, and it isn't a command line tool, it might make sense to use Xamarin.Mac as your packager / launcher.

    If you set full or system as your target framework you should be able to get most things working under Xamarin.Mac.

  • DancingDevDancingDev Member ✭✭
    edited November 25

    Yes I imagine I could wrap the existing console app that I have in a xamarin.mac app, but I think that that will require a lot more work, that I'm not willing to take on just yet.

    I've found docs.microsoft.com/en-us/xamarin/mac/app-fundamentals/console#creating-the-console-app which suggests I can add some of the xamarin dlls to an existing console app and then "run it", but the example shows that the output is a .exe and doesn't indicate how to run it. So I'm not sure that the article is of any use.

    The beauty of mkbundle is that it will pack the mono runtime into the binary itself so that you don't have to have the mono runtime installed on the target machine.

    EDIT: oh, the article continues on to use mkbundle... I'll investigate if I've missed anything.

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    Xamarin.Mac apps also bundle mono into the app bundle. I understand not wanting to bite off all of that working for signing.

    The hard part of headless is handling the libxammac.dylib. In a single file configuring, there isn't a great place to put it.

  • DancingDevDancingDev Member ✭✭

    The resulting bundle from that example crashes and cannot be signed. Could I bother you try run through the steps in that article?

    mkbundle --simple -o /tmp/consoleapp consoleapp.exe --library libxammac.dylib --config /Library/Frameworks/Mono.framework/Versions/Current/etc/mono/config --machine-config /Library/Frameworks/Mono.framework/Versions/Current//etc/mono/4.5/machine.config
    
    2019-11-25 15:47:50.749 testConsoleApp[7301:432998] 
    Unhandled Exception:
    System.MethodAccessException: Method `Mono.SystemDependencyProvider.Initialize()' is inaccessible from method `ObjCRuntime.Runtime.Initialize(ObjCRuntime.Runtime/InitializationOptions*)'
      at (wrapper managed-to-native) System.Object.__icall_wrapper_mono_throw_method_access(intptr,intptr)
      at ObjCRuntime.Runtime.Initialize (ObjCRuntime.Runtime+InitializationOptions* options) [0x00290] in <dd87740fbb6340a2b184810a7b4411e5>:0 
    --- End of stack trace from previous location where exception was thrown ---
    
      at (wrapper managed-to-native) System.Object.wrapper_native_0x108e978e0()
      at ObjCRuntime.Runtime.EnsureInitialized () [0x00051] in <dd87740fbb6340a2b184810a7b4411e5>:0 
      at AppKit.NSApplication.Init () [0x00016] in <dd87740fbb6340a2b184810a7b4411e5>:0 
      at testConsoleApp.MainClass.Main (System.String[] args) [0x00000] in <c0ff4461ac6b4913b5aca0774c27944e>:0 
    [ERROR] FATAL UNHANDLED EXCEPTION: System.MethodAccessException: Method `Mono.SystemDependencyProvider.Initialize()' is inaccessible from method `ObjCRuntime.Runtime.Initialize(ObjCRuntime.Runtime/InitializationOptions*)'
      at (wrapper managed-to-native) System.Object.__icall_wrapper_mono_throw_method_access(intptr,intptr)
      at ObjCRuntime.Runtime.Initialize (ObjCRuntime.Runtime+InitializationOptions* options) [0x00290] in <dd87740fbb6340a2b184810a7b4411e5>:0 
    --- End of stack trace from previous location where exception was thrown ---
    
      at (wrapper managed-to-native) System.Object.wrapper_native_0x108e978e0()
      at ObjCRuntime.Runtime.EnsureInitialized () [0x00051] in <dd87740fbb6340a2b184810a7b4411e5>:0 
      at AppKit.NSApplication.Init () [0x00016] in <dd87740fbb6340a2b184810a7b4411e5>:0 
      at testConsoleApp.MainClass.Main (System.String[] args) [0x00000] in <c0ff4461ac6b4913b5aca0774c27944e>:0 
    
  • DancingDevDancingDev Member ✭✭

    @ChrisHamons
    I also found and old thread you commented on https://forums.xamarin.com/discussion/66916/how-to-construct-c-console-application-which-will-be-able-to-consume-mac-specific-api-keychain

    Do you have any updated articles for how one best would port or wrap an existing .net console app in a xamarin mac app?

    I want to at least understand how much work might be required. I don't need to pass command line args to my "daemon", it will be launched via a plist in /Library/LaunchDameons/

    Should I start a new thread asking that specific question? Do you know of an open one that I could add to?

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai
    edited December 5

    So when I checked the closest thing we have to a sample on this, I found out it was broken.

    After fixing that up - https://github.com/xamarin/mac-samples/pull/122

    I have a few steps you can consider doing:

    • Create a new Xamarin.Mac application
    • Delete everything but Main.cs, Entitlements.plist and Info.plist from the project (AppDelegate, Main.storyboard, ViewControler.cs)
    • Open Info.plist in a text editor and delete:
      <key>NSPrincipalClass</key>
      <string>NSApplication</string>
      <key>NSMainStoryboardFile</key>
      <string>Main</string>
      <key>XSAppIconAssets</key>
      <string>Assets.xcassets/AppIcon.appiconset</string>
    
    • Add this to your Info.plist (Prevents icon from showing up in dock)
      <key>LSUIElement</key>
      <true/>
    

    Then you can startup your application in Main with:

    static void Main ()
    {
        NSApplication.Init ();
        // Your stuff here
    

    Once you have that running, you can look into signing / hardening using the XM tools built in.

Sign In or Register to comment.