Browser Extension Native Messaging Security

For browser extension to communicate with a native app there must be a helper app. It is launched by the browser and the communication happens via stdin and stdout. I wrote such a helper app in Swift, it works.

I'd like to add security checks to the helper app.

  1. To make sure that the parent process is one of the approved browsers - I can do this with NSRunningApplication(processIdentifier: getppid())?.bundleIdentifier
  2. To make sure the parent process has valid signature
  3. To make sure that the other peer of the stdin/stdout pipes is the parent process

Do you know ways to achieve 2 and 3? Does the way I am doing 1 look correct to you?

Answered by DTS Engineer in 798185022

Well, that’s kinda depressing, at least from a security standpoint )-: But I’m glad it’s not Safari that sent you down this path (-:

Anyway, it rules out XPC options, so let’s return to your initial questions.

Parent Process ID

The result from getppid is not particularly trustworthy. There are two common situations where it’s wrong:

  • If the parent process exits, you get reparented. The rules for that are… Quinn literally dusts of his copy of Advanced Programming in the Unix Environment that the parent pid changes to 1.

  • If you’re run under the debugger, the parent pid is that of the debugger.

Keep in mind that process IDs are, in general, subject to process ID reuse attacks.

Bundle ID

Don’t use bundle IDs for anything related to security. I can quite happily create an app with a bundle ID of com.apple.Finder!

Instead, rely on the code signature; see below for info on how to get access to that.

If you want to identify code on the Mac, use its designated requirement. I talk about this in gory detail in TN3127 Inside Code Signing: Requirements.

Code Signature

The best way to get the code signature fro a process is via its audit token. See the code here.

You don’t have an audit token )-: so you’ll have to use kSecGuestAttributePid. That’s less than ideal due to the pid reuse vulnerability.

Other End of the Pipe

This requirement isn’t valid because, due to file descriptor sharing, the other end of a pipe might be available to multiple processes. Generally, solutions in this space yield one of two values:

  • All processes at the other end of the pipe.

  • The process that created the other end of the pipe.

I’m not aware of any good way to do this with a pipe. If the remote peer just happened to use a Unix domain socket, you can do it with LOCAL_PEERTOKEN, or older stuff like LOCAL_PEERPID and LOCAL_PEERCRED.

If the remote peer really did use a pipe then I think the only option is to use libproc grovel through its file descriptors. This is not easy, and will almost certainly require privilege escalation, so it’s hard to recommend.

Share and Enjoy

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

This is macOS right?

For browser extension to communicate with a native app there must be a helper app. It is launched by the browser and the communication happens via stdin and stdout.

I need to get a better handle on how these parts fit together. In general, an Apple platforms, an “app” is something that the user double clicks in the Finder, and thus stdin and stdout are irrelevant. So is this actually an app? Or is it sort of command-line tool? And can you point me to the docs that explain how this process gets launched?

Oh, and btw, there are probably solutions to all three items on your list. I’m happy to go into details at some point, but right now I’m more concerned about the overall security of your IPC mechanism. I would be much happier if you could get this running using XPC, where the rules are clearer [1].

Share and Enjoy

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

[1] See Validating Signature Of XPC Process.

This is macOS

For me this is sort of a command line tool. I created it from Xcode app template, but it has no UI and Application is agent (UIElement) YES

The docs: https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging/

Chrome starts each native messaging host in a separate process and communicates with it using standard input (stdin) and standard output (stdout).

In this example Chrome launches it. If I call getppid(), it gives me Chrome's pid.

I use XPC for communication between my "processes". But I can't choose how Chromium-based browsers communicates with my helper "app", that's why stdin and stdout This helper "app" that Chrome launches connects to my main app with XPC and transfers data. It works as a bridge between them.

Accepted Answer

Well, that’s kinda depressing, at least from a security standpoint )-: But I’m glad it’s not Safari that sent you down this path (-:

Anyway, it rules out XPC options, so let’s return to your initial questions.

Parent Process ID

The result from getppid is not particularly trustworthy. There are two common situations where it’s wrong:

  • If the parent process exits, you get reparented. The rules for that are… Quinn literally dusts of his copy of Advanced Programming in the Unix Environment that the parent pid changes to 1.

  • If you’re run under the debugger, the parent pid is that of the debugger.

Keep in mind that process IDs are, in general, subject to process ID reuse attacks.

Bundle ID

Don’t use bundle IDs for anything related to security. I can quite happily create an app with a bundle ID of com.apple.Finder!

Instead, rely on the code signature; see below for info on how to get access to that.

If you want to identify code on the Mac, use its designated requirement. I talk about this in gory detail in TN3127 Inside Code Signing: Requirements.

Code Signature

The best way to get the code signature fro a process is via its audit token. See the code here.

You don’t have an audit token )-: so you’ll have to use kSecGuestAttributePid. That’s less than ideal due to the pid reuse vulnerability.

Other End of the Pipe

This requirement isn’t valid because, due to file descriptor sharing, the other end of a pipe might be available to multiple processes. Generally, solutions in this space yield one of two values:

  • All processes at the other end of the pipe.

  • The process that created the other end of the pipe.

I’m not aware of any good way to do this with a pipe. If the remote peer just happened to use a Unix domain socket, you can do it with LOCAL_PEERTOKEN, or older stuff like LOCAL_PEERPID and LOCAL_PEERCRED.

If the remote peer really did use a pipe then I think the only option is to use libproc grovel through its file descriptors. This is not easy, and will almost certainly require privilege escalation, so it’s hard to recommend.

Share and Enjoy

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

I'm missing info for the socket part..

Is this how I would get PID? getsockopt(socket_id, SOL_LOCAL, LOCAL_PEERPID, &pid, &len)

How would I get socket_id in this case? I use FileHandle.standardInput to read data, I don't create sockets myself

Is this how I would get PID?

You could do it that way, but it’s better to use LOCAL_PEERTOKEN, which gives you an audit token. For example:

audit_token_t audit;
socklen_t auditLen = sizeof(audit);
int ok = getsockopt(fd, SOL_LOCAL, LOCAL_PEERTOKEN, &audit, &auditLen) >= 0;
… handle error …

You can then feed the audit token into other APIs, like kSecGuestAttributeAudit.

How would I get socket_id in this case?

In a command-line tool the standard input is represented in three ways:

  • As file descriptor 0, that is, STDIN_FILENO

  • As a FILE * of stdin

  • As FileHandle.standardInput

If you’re going to call getsockopt, you want the first one.

IMPORTANT To reiterate, there’s no guarantee that your stdin is a Unix domain socket. The folks at the other end could have have chosen to use a pipe.

Share and Enjoy

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

Browser Extension Native Messaging Security
 
 
Q