-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Factors out XPC server message acceptance logic #39
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||
// SecureMessageAcceptor.swift | ||||||||||||||||||||||||||||||
// SecureXPC | ||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||
// Created by Josh Kaplan on 2022-01-06 | ||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
import Foundation | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
/// Accepts messages which meet the provided code signing requirements. | ||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||
/// Uses undocumented functionality prior to macOS 11. | ||||||||||||||||||||||||||||||
internal struct SecureMessageAcceptor { | ||||||||||||||||||||||||||||||
/// At least one of these code signing requirements must be met in order for the message to be accepted | ||||||||||||||||||||||||||||||
internal let requirements: [SecRequirement] | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
func acceptMessage(connection: xpc_connection_t, message: xpc_object_t) -> Bool { | ||||||||||||||||||||||||||||||
// Get the code representing the client | ||||||||||||||||||||||||||||||
var code: SecCode? | ||||||||||||||||||||||||||||||
if #available(macOS 11, *) { // publicly documented, but only available since macOS 11 | ||||||||||||||||||||||||||||||
SecCodeCreateWithXPCMessage(message, SecCSFlags(), &code) | ||||||||||||||||||||||||||||||
} else { // private undocumented function: xpc_connection_get_audit_token, available on prior versions of macOS | ||||||||||||||||||||||||||||||
if var auditToken = xpc_connection_get_audit_token(connection) { | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you add an Better yet, you should just flip this with a guard as well: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding is Incorporated feedback on second point about guard statement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AH I didn't know that was the expected param type. Usually optional promotion happens, but not in the case of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately it's not allowed. Xcode outputs:
|
||||||||||||||||||||||||||||||
let tokenData = NSData(bytes: &auditToken, length: MemoryLayout.size(ofValue: auditToken)) | ||||||||||||||||||||||||||||||
let attributes = [kSecGuestAttributeAudit : tokenData] as NSDictionary | ||||||||||||||||||||||||||||||
SecCodeCopyGuestWithAttributes(nil, attributes, SecCSFlags(), &code) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Accept message if code is valid and meets any of the client requirements | ||||||||||||||||||||||||||||||
var accept = false | ||||||||||||||||||||||||||||||
if let code = code { | ||||||||||||||||||||||||||||||
for requirement in self.requirements { | ||||||||||||||||||||||||||||||
if SecCodeCheckValidity(code, SecCSFlags(), requirement) == errSecSuccess { | ||||||||||||||||||||||||||||||
accept = true | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return accept | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
Suggested change
|
||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
/// Wrapper around the private undocumented function `void xpc_connection_get_audit_token(xpc_connection_t, audit_token_t *)`. | ||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||
/// The private undocumented function will attempt to be dynamically loaded and then invoked. If no function exists with this name `nil` will be returned. If | ||||||||||||||||||||||||||||||
/// the function does exist, but does not match the expected signature, the process calling this function is expected to crash. However, because this is only | ||||||||||||||||||||||||||||||
/// called on older versions of macOS which are expected to have a stable non-changing API this is very unlikely to occur. | ||||||||||||||||||||||||||||||
/// | ||||||||||||||||||||||||||||||
/// - Parameters: | ||||||||||||||||||||||||||||||
/// - _: The connection for which the audit token will be retrieved for. | ||||||||||||||||||||||||||||||
/// - Returns: The audit token or `nil` if the function could not be called. | ||||||||||||||||||||||||||||||
private func xpc_connection_get_audit_token(_ connection: xpc_connection_t) -> audit_token_t? { | ||||||||||||||||||||||||||||||
// Attempt to dynamically load the function | ||||||||||||||||||||||||||||||
guard let handle = dlopen(nil, RTLD_LAZY) else { | ||||||||||||||||||||||||||||||
return nil | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
defer { dlclose(handle) } | ||||||||||||||||||||||||||||||
guard let sym = dlsym(handle, "xpc_connection_get_audit_token") else { | ||||||||||||||||||||||||||||||
return nil | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
typealias functionSignature = @convention(c) (xpc_connection_t, UnsafeMutablePointer<audit_token_t>) -> Void | ||||||||||||||||||||||||||||||
Comment on lines
+49
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This gets called on every message. It's probably worth caching the resulting function, thought that would be best done in a follow up PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Had the same thought while refactoring this, but didn't want to make such a change as part of this PR. |
||||||||||||||||||||||||||||||
let function = unsafeBitCast(sym, to: functionSignature.self) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Call the function | ||||||||||||||||||||||||||||||
var token = audit_token_t() | ||||||||||||||||||||||||||||||
function(connection, &token) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return token | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be behind a protocol, so you can make an "always allow" implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally had it written that way, but it ended up with quite a bit more code across multiple files so it didn't seem worth the complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. If we need it in the future, we can just do an "extract interface" refactor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, can absolutely refactor in the future if warranted