Securing XPC Daemon Communication from Authorization Plugin

I'm working on securing communication between an Authorization Plugin and an XPC daemon, and I’d appreciate some guidance on best practices and troubleshooting.

The current design which, I’ve implemented a custom Authorization Plugin for step-up authentication, which is loaded by Authorization Services at the loginwindow (inside SecurityAgent). This plugin acts as an XPC client and connects to a custom XPC daemon.

Setup Details

1. XPC Daemon

  • Runs as root (LaunchDaemon)
  • Not sandboxed (my understanding is that root daemons typically don’t run sandboxed—please correct me if this is wrong)
  • Mach service: com.roboInc.AuthXpcDaemon
  • Bundle identifier: com.roboInc.OfflineAuthXpcDaemon

2. Authorization Plugin

  • Bundle identifier: com.roboInc.AuthPlugin
  • Loaded by SecurityAgent during login

3. Code Signing Both plugin and daemon are signed using a development certificate

What I’m Trying to Achieve

I want to secure the XPC communication so that:

  • The daemon only accepts connections from trusted clients
  • The plugin only connects to the legitimate daemon
  • Communication is protected against unauthorized access

The Issue I'm facing

I attempted to validate code signatures using:

  • SecRequirementCreateWithString
  • SecCodeCopyGuestWithAttributes
  • SecCodeCheckValidity

However, validation consistently fails with:

-67050 (errSecCSReqFailed)

Could you please help here

  1. What is the recommended way to securely authenticate an Authorization Plugin (running inside SecurityAgent) to a privileged XPC daemon?

  2. Since the plugin runs inside SecurityAgent, how can the daemon reliably distinguish my plugin from other plugins?

  3. What is the correct approach to building a SecRequirement in this scenario?

Any guidance, examples, or pointers would be greatly appreciated. Thanks in advance!

Answered by DTS Engineer in 880794022
my understanding is that root daemons typically don’t run sandboxed

Correct. While it is possible to enable the App Sandbox on a daemon, folks don’t normally do that.

The plugin only connects to the legitimate daemon

The canonical way to do that is by setting the privileged flag. I have a link to an explanation of that in XPC Resources.

You could also validate the peer’s signature. Again, I have a link to info about that in XPC Resources

IMPORTANT This works in this direction because you control the daemon’s main executable.

The daemon only accepts connections from trusted clients

There isn’t a good way to achieve this goal, presuming that this set of trusted clients includes an authorisation plug-in. The issue is that authorisation plug-ins are in-memory plug-ins, so you don’t control the main executable of the process in which the plug-in runs. Rather, it’s loaded by a process running a system executable (well, actually a small set of system executables). That presents two problems:

  • The exact identity of those executables isn’t considered API. It has changed in the past and it may well change again in the future. So you can either apply a very loose constraint (any Apple code) or apply a strict constraint and run the risk of things failing in the future.
  • The same executables are used to load all authorisation, so your daemon can’t distinguish between your plug-in and any other authorisation plug-in.
However, validation consistently fails with … errSecCSReqFailed

It’s hard to say what’s going on there without seeing the specific requirement you’re using. In general:

  • See TN3127 Inside Code Signing: Requirements for lots of background about code signing requirements.
  • As you bring this up, syntax check your requirement using csreq. See the csreq man page.
  • And then test it using codesign. TN3127 has examples of this.

Share and Enjoy

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

my understanding is that root daemons typically don’t run sandboxed

Correct. While it is possible to enable the App Sandbox on a daemon, folks don’t normally do that.

The plugin only connects to the legitimate daemon

The canonical way to do that is by setting the privileged flag. I have a link to an explanation of that in XPC Resources.

You could also validate the peer’s signature. Again, I have a link to info about that in XPC Resources

IMPORTANT This works in this direction because you control the daemon’s main executable.

The daemon only accepts connections from trusted clients

There isn’t a good way to achieve this goal, presuming that this set of trusted clients includes an authorisation plug-in. The issue is that authorisation plug-ins are in-memory plug-ins, so you don’t control the main executable of the process in which the plug-in runs. Rather, it’s loaded by a process running a system executable (well, actually a small set of system executables). That presents two problems:

  • The exact identity of those executables isn’t considered API. It has changed in the past and it may well change again in the future. So you can either apply a very loose constraint (any Apple code) or apply a strict constraint and run the risk of things failing in the future.
  • The same executables are used to load all authorisation, so your daemon can’t distinguish between your plug-in and any other authorisation plug-in.
However, validation consistently fails with … errSecCSReqFailed

It’s hard to say what’s going on there without seeing the specific requirement you’re using. In general:

  • See TN3127 Inside Code Signing: Requirements for lots of background about code signing requirements.
  • As you bring this up, syntax check your requirement using csreq. See the csreq man page.
  • And then test it using codesign. TN3127 has examples of this.

Share and Enjoy

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

Thanks for the detailed information. I’ll take a look and follow up.

_It’s hard to say what’s going on there without seeing the specific requirement you’re using_

Here are the specific requirement i've tried

 anchor apple generic and ( identifier "com.apple.SecurityAgentHelper.arm64"
or identifier "com.apple.loginwindow"
or identifier "com.apple.authorizationhost")

Thanks!

Thanks for the detailed information. I’ll take a look and follow up.

_It’s hard to say what’s going on there without seeing the specific requirement you’re using_

Here are the specific requirement i'm using from the Authorization Plugin(xpc client)

"com.apple.SecurityAgentHelper.arm64",
"com.apple.loginwindow",
"com.apple.authorizationhost",
"com.apple.SecurityAgent",
bash-3.2$ codesign --display -r - /System/Library/CoreServices/loginwindow.app
Executable=/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow
designated => identifier "com.apple.loginwindow" and anchor apple

I attempted to apply a loose constraint (accepting any Apple-signed code), but it still fails. Please let me know if I’m approaching the loose constraint correctly.

I was able to block other applications(created a custom app) trying to connect to my daemon. However, during the login and lock screen flows, it fails with error -67050 (errSecCSReqFailed). Is there a way to identify whether the client originates from Authorization Services or SFAuthorization and ignore the security check?

It would be helpful if you could share any info.

Thanks!

Here are the specific requirement i've tried

So anchor apple generic is definitely wrong. Consider this quote from Code Signing Requirement Language:

For Apple’s own code, signed by Apple, you can use the short form

anchor apple

For code signed by Apple, including code signed using a signing certificate issued by Apple to other developers, use the form

anchor apple generic

If your goal is security, you don’t want to mix these up!

Here are the specific requirement i'm using from the Authorization Plugin (xpc client)

From the client? Maybe I’ve misunderstood this, but that seems backwards. You want to apply these constraints on the server for connections coming from the client.

Consider this:

% csreq -r '=anchor apple and (identifier "com.apple.authorizationhosthelper.arm64" or identifier "com.apple.authorizationhosthelper.x86_64")' -b "tmp.req"

Finder fails to satisfy this requirement:

% codesign --verify -v -R "tmp.req" "/System/Library/CoreServices/Finder.app"     
…
test-requirement: code failed to satisfy specified code requirement(s)

but both authorizationhosthelper executables do:

% codesign --verify -v -R "tmp.req" "/System/Library/Frameworks/Security.framework/Versions/A/MachServices/authorizationhost.bundle/Contents/XPCServices/authorizationhosthelper.arm64.xpc"
…
…/authorizationhosthelper.arm64.xpc: explicit requirement satisfied
% 
% codesign --verify -v -R "tmp.req" "/System/Library/Frameworks/Security.framework/Versions/A/MachServices/authorizationhost.bundle/Contents/XPCServices/authorizationhosthelper.x86_64.xpc"
…
…/authorizationhosthelper.x86_64.xpc: explicit requirement satisfied

WARNING But my previous comment about long-term compatibility still applies here. The code signing identifiers of the various processes that host your authorisation plug-in are not considered API. They have changed in the past and may well change in the future.

Is there a way to identify whether the client originates from Authorization Services or SFAuthorization … ?

How would you expect that to work? Your plug-in is loaded into a system process. How can your daemon, as the XPC listener, identify which code within that process sent the XPC message. It’s conceptually impossible.

The best you can do is constrain the process it originated from but:

  • Such constraints are brittle, as I’ve explained above.
  • Such constraints don’t achieve your ultimate goal because the same process is used to load all authorisation plug-ins.

Now I’d argue that defending yourself from rogue authorisation plug-ins is pointless, because if an attacker is in a position to install an authorisation plug-in the game is basically over, but that’s a policy question that’s firmly in your court.

Share and Enjoy

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

Thank you again for the prompt response and for sharing the additional information.

How would you expect that to work? Your plug-in is loaded into a system process. How can your daemon, as the XPC listener, identify which code within that process sent the XPC message. It’s conceptually impossible.

I acknowledge the limitation,I wouldn’t try to distinguish specific code within the process, since that isn’t feasible. Instead, I’d skip client validation for the authorization plug-in path. For other clients communicating with the daemon, I already enforce validation using proper identifiers and code-signing checks and works. Since the core logic is shared, the idea is to rely on those validated clients and bypass the check only for the authorization process.

Now I’d argue that defending yourself from rogue authorisation plug-ins is pointless, because if an attacker is in a position to install an authorisation plug-in the game is basically over, but that’s a policy question that’s firmly in your court.

I completely agree, if someone can install an authorization plug-in, they already have deep access. My intent was mainly to get confirmation and close for our internal security reference.considerations.

Thanks!

I’m still not entirely sure what stage you’re at here. You started this thread talking about manually checking requirements, which is not what I recommend when dealing with XPC. Rather, you should apply the requirement directly to the XPC connection, as explained in this forums post.

With that in mind, it’s a question of crafting the right requirement. There are two parts to this:

  • Identifying your clients
  • Identifying your code loaded into system clients

Identifying your clients should be straightforward. You start with the DR of those clients and then adjust the Boolean logic to suit your specific needs.

It’s not possible to identify your code loaded into system clients, so the best you can do is identify those systems clients themselves. And there’s no good way to do that:

  • You could use the client’s designated requirement (DR), but that might break if the system changes the context in which it loads your authorisation plug-ins (as has happened in the past).
  • You could use a much broader requirement, like anchor apple, but that’s significantly less secure.

I recommend that you prototype your requirements using csreq to syntax check and codesign to evaluate the requirements, as I’ve explained above.

I recommend that you read TN3127 Inside Code Signing: Requirements, which talks about this stuff in gory detail.

I think that’ll be sufficient to get you up and running. If not, please reply here with specific follow-up questions.

Share and Enjoy

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

Securing XPC Daemon Communication from Authorization Plugin
 
 
Q