Upgrading an SMAppService daemon and changing the plist

Hi,

We have a macOS application that contains a helper daemon that was registered with launchd using the SMAppService API and for the most part its been working okay until we tried to release an update that added an XPC service to the daemon. When users try to upgrade the software, the new service now fails to launch due to a launch constraint violation.

The Console log shows the following error after the upgrade:

AMFI: Launch Constraint Violation (enforcing), error info: c[5]p[1]m[1]e[0], (Constraint not matched) launching proc[vc: 6 pid: 1422]: /Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon, launch type 0, failure proc [vc: 6 pid: 1422]: /Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon

The service plist before the upgrade looked like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
        <dict>
                <key>AssociatedBundleIdentifiers</key>
                <string>org.mozilla.macos.FirefoxVPN</string>
                <key>Label</key>
                <string>org.mozilla.macos.FirefoxVPN.service</string>
                <key>BundleProgram</key>
                <string>Contents/MacOS/Mozilla VPN</string>
                <key>ProgramArguments</key>
                <array>
                        <string>Mozilla VPN</string>
                        <string>macosdaemon</string>
                </array>
                <key>UserName</key>
                <string>root</string>
                <key>RunAtLoad</key>
                <true/>
                <key>KeepAlive</key>
                <true/>
                <key>SoftResourceLimits</key>
                <dict>
                        <key>NumberOfFiles</key>
                        <integer>1024</integer>
                </dict>
                <key>StandardErrorPath</key>
                <string>/var/log/mozillavpn/stderr.log</string>
        </dict>
</plist>

The updated plist changes the BundleProgram, removes ProgramArguments and adds MachServices, which results in the following plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
        <dict>
                <key>AssociatedBundleIdentifiers</key>
                <string>org.mozilla.macos.FirefoxVPN</string>
                <key>Label</key>
                <string>org.mozilla.macos.FirefoxVPN.service</string>
                <key>BundleProgram</key>
                <string>Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon</string>
                <key>MachServices</key>
                <dict>
                        <key>org.mozilla.macos.FirefoxVPN.service</key>
                        <true/>
                </dict>
                <key>UserName</key>
                <string>root</string>
                <key>RunAtLoad</key>
                <true/>
                <key>KeepAlive</key>
                <true/>
                <key>SoftResourceLimits</key>
                <dict>
                        <key>NumberOfFiles</key>
                        <integer>1024</integer>
                </dict>
                <key>StandardErrorPath</key>
                <string>/var/log/mozillavpn/stderr.log</string>
        </dict>
</plist>

On a fresh machine/VM, this works just fine, we only encounter the Launch Constraint Violation when upgrading from one version to the next.

We were hoping that the service could have been upgraded by calling unregisterWithCompletionHandler first, but this seems have no effect on the bug.

So, I guess my questions are:

  • Is there a way to diagnose what the launch constraints are for a service, and which why the constraints are being violated?
  • How does one go about changing the plist for a daemon installed via SMAppService?

Thanks, Naomi

Answered by DTS Engineer in 851292022

Ah, launch constraint errors, they’re always fun. Lemme provide some hints for specific fields. But first…

IMPORTANT The following uses a lot of concepts from the LightweightCodeRequirements. It’d be good to read that first.

So, about those fields:

  • vc: 6 — This is the validation category. You can actually find the corresponding values in the macOS SDK. Search <Kernel/kern/cs_blobs.h> for CS_VALIDATION_CATEGORY values. In this case, 6 is CS_VALIDATION_CATEGORY_DEVELOPER_ID, which is exactly what you’d expect.

    Validation categories are also part of the LightweightCodeRequirements framework, expressed via the ValidationCategory type.

  • pid: 1422 — That’s obviously a process ID.

  • /Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon — That one’s obvious too (-:

  • launch type 0 — This value is of type cs_launch_type_t, which you can also find in <Kernel/kern/cs_blobs.h>. 0 is the value CS_LAUNCH_TYPE_NONE.

  • c[5] — This identifies the type of constraint. The value of 5 is a LightweightCodeRequirements (LWCR) spawn constraint.

  • p[1] — This identies the process the constraint applies to. The value of 1 is the process itself (2 is the parent, 3 is the responsible process).

  • m[1] — This is what happening with the matching. The value here, 1, indicates that there was no match.

  • e[0] — This is an error code. The value of 0 indicates no error (that is, the operation ran successfully and generated no match).

As to what this actually means, I’m not sure. My best guess is that launchctl is applying a spawn constraint to invocation of your daemon and that’s somehow failing. However, I’m not sure how to debug that further.


Oh, wait, you changed the name of the executable. Did that coincidentally change its code signing identifier? If so, that’ll have changed its designated requirement (DR), and I can easily imagine that triggering this issue.

If you get the DR of the old executable and then check whether the new executable satisfies that DR, does it?

See TN3127 Inside Code Signing: Requirements for more background on DRs.

Share and Enjoy

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

Ah, launch constraint errors, they’re always fun. Lemme provide some hints for specific fields. But first…

IMPORTANT The following uses a lot of concepts from the LightweightCodeRequirements. It’d be good to read that first.

So, about those fields:

  • vc: 6 — This is the validation category. You can actually find the corresponding values in the macOS SDK. Search <Kernel/kern/cs_blobs.h> for CS_VALIDATION_CATEGORY values. In this case, 6 is CS_VALIDATION_CATEGORY_DEVELOPER_ID, which is exactly what you’d expect.

    Validation categories are also part of the LightweightCodeRequirements framework, expressed via the ValidationCategory type.

  • pid: 1422 — That’s obviously a process ID.

  • /Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon — That one’s obvious too (-:

  • launch type 0 — This value is of type cs_launch_type_t, which you can also find in <Kernel/kern/cs_blobs.h>. 0 is the value CS_LAUNCH_TYPE_NONE.

  • c[5] — This identifies the type of constraint. The value of 5 is a LightweightCodeRequirements (LWCR) spawn constraint.

  • p[1] — This identies the process the constraint applies to. The value of 1 is the process itself (2 is the parent, 3 is the responsible process).

  • m[1] — This is what happening with the matching. The value here, 1, indicates that there was no match.

  • e[0] — This is an error code. The value of 0 indicates no error (that is, the operation ran successfully and generated no match).

As to what this actually means, I’m not sure. My best guess is that launchctl is applying a spawn constraint to invocation of your daemon and that’s somehow failing. However, I’m not sure how to debug that further.


Oh, wait, you changed the name of the executable. Did that coincidentally change its code signing identifier? If so, that’ll have changed its designated requirement (DR), and I can easily imagine that triggering this issue.

If you get the DR of the old executable and then check whether the new executable satisfies that DR, does it?

See TN3127 Inside Code Signing: Requirements for more background on DRs.

Share and Enjoy

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

Yes, the code signing identifier of the daemon changed as a part of the upgrade. We split the application from a monolithic binary into separate GUI and daemon executables.

The designated requirement of the monolithic GUI hasn't changed:

Executable=/Applications/Mozilla VPN.app/Contents/MacOS/Mozilla VPN
designated => identifier "org.mozilla.macos.FirefoxVPN" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "43AQ936H96"

And the designated requirement of the daemon has a different identifier:

Executable=/Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon
designated => identifier "org.mozilla.macos.FirefoxVPN.daemon" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "43AQ936H96"

Is there a way to tell launchd about the change? We have also tried using a postinstall script to unload the daemon using launchctl bootout system/org.mozilla.macos.FirefoxVPN.service to no avail.

There does appear to be some way to get launchd to accept the upgrade. For example, the following steps get back to a working machine after the upgrade:

  1. Disable our app's Allow in the Background permission in the Login Items settings.
  2. Delete the app bundle off disk and empty the trash.
  3. Close and re-open the System Settings to the Login Items page and wait for our app's entry to disappear.
  4. Re-install the application again and it works as expected.
We split the application from a monolithic binary into separate GUI and daemon executables.

Oh, yeah, that makes sense now that I look back at the property lists in your first post.

Is there a way to tell launchd about the change?

Not that I’m aware of.

Just as an experiment, if you override the code signing identifier of the daemon to match that of the main app, does that fix this problem?

If you’er using code, change the code signing identifier by setting the Other Code Signing Flags build setting to [ $(inherited), --identifier, org.mozilla.macos.FirefoxVPN].

IMPORTANT I see a lot of folks put a lot of weird stuff in the Other Code Signing Flags build setting. I recommend against that in general, but it is a reasonable way to override the code signing identifier in a program that has no bundle ID.

Share and Enjoy

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

We don't typically use Xcode for development of this app (our build environment uses CMake and Ninja for the most part), but adding --identifier org.mozilla.macos.FirefoxVPN to the codesign arguments to the daemon binary seems to have gotten the service to run successfully across the upgrade.

Upgrading an SMAppService daemon and changing the plist
 
 
Q