-
Notifications
You must be signed in to change notification settings - Fork 4
Add assessment for blessing from a sandboxed program #21
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -38,7 +38,8 @@ public struct BlessError: Error { | |
| toolAssessor.infoPropertyListAuthorizedClients(type: .bundled), // 7 & 8 - bundled | ||
| toolAssessor.infoPropertyListAuthorizedClients(type: .installed), // 7 & 8 - bundled | ||
| toolAssessor.infoPropertyListBundleVersion(), // 9 | ||
| appAssessor.infoPropertyList(bundledHelperToolLocation: toolAssessor.bundledLocation, label: label) // 10 | ||
| appAssessor.infoPropertyList(bundledHelperToolLocation: toolAssessor.bundledLocation, label: label), // 10 | ||
| appAssessor.isNotSandboxed(), // 11 | ||
| ] | ||
| } | ||
| } | ||
|
|
@@ -528,4 +529,20 @@ fileprivate struct AppAssessor { | |
| """) | ||
| } | ||
| } | ||
|
|
||
| // 11 | ||
| func isNotSandboxed() -> Assessment { | ||
| if ProcessInfo.processInfo.isSandboxed { | ||
| // XPC services are usually found in a path like ".../MyApp.app/Contents/XPCServices/Bar.xpc/Contents/MacOS/MyService" | ||
| let isXPCService = Bundle.main.executableURL?.deletingLastPathComponent().path.hasSuffix(".xpc/Contents/MacOS") ?? false | ||
| let programType = isXPCService ? "XPC service" : "App" | ||
|
Author
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. What do you think of applying this language change more broadly, to make more sense in the context of someone following the EvenBetterAuthorizationSample, who's trying to use Blessed from an XPC helper? |
||
|
|
||
| return .notSatisfied(explanation: """ | ||
| This \(programType) is sandboxed, which will cause SMJobBless() to always get denied. | ||
| Helper tools can only be blessed from a non-sandboxed \(programType). | ||
| """) | ||
| } | ||
|
|
||
| return .satisfied | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,7 +28,7 @@ extension ProcessInfo { | |
| return false | ||
| } | ||
|
|
||
| guard CFGetTypeID(entitlement) == CFBooleanGetTypeID(), let boolValue = (entitlement as? Bool) else { | ||
| guard let boolValue = entitlement as? Bool else { | ||
|
Author
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. The explicit import Foundation
// `as? Bool` already rejects non-numeric `CFTypeRef` values, like a CFString,
// so we don't need to do our own `CFGetTypeID()` check
let cfString: CFTypeRef = "abc" as CFString
print(cfString as? Bool as Any) // => nil
// Bools cast successfully, as expected
let cfFalse: CFTypeRef = kCFBooleanFalse
let cfTrue: CFTypeRef = kCFBooleanTrue
print(cfFalse as? Bool as Any) // => Optional(false)
print(cfTrue as? Bool as Any) // => Optional(true)
Author
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. Spoke to soon. I forgot how Core Foundation and Foundation are rather loose around the distinction between numbers and actual booleans. I guess that comes from the C heritage. import Foundation
func createCFNumberInt(_ value: Int) -> CFNumber {
var value = value
return CFNumberCreate(kCFAllocatorDefault, .intType, &value)
}
let zero: CFTypeRef = createCFNumberInt(0)
let one: CFTypeRef = createCFNumberInt(1)
print(CFGetTypeID(zero) == CFNumberGetTypeID()) // => true, obviously
print(CFGetTypeID(zero) == CFBooleanGetTypeID()) // => false
// Surprisingly, 0/1 can be treated as booleans:
print(zero as? Bool as Any) // => Optional(false)
print(one as? Bool as Any) // => Optional(true)Still, I think being more permissive and accepting |
||
| // The entitlement value must be a boolean value. If it's not, then it's presumbly not sandboxed. | ||
| return false | ||
| } | ||
|
|
||
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.
Kinda janky, but it's the best signal I could find for detecting if this code is running an App or XPC service.
I tried looking at the
ProcessInfo.processInfo.environment, but interestingly, even main Apps have theXPC_SERVICEkey set.Another idea was to check the parent process ID. GUI apps are always owned by
launchd(pid 1), whereas XPC services are owned by their GUI apps. But that isn't true for programs running from Xcode, which will cause both the App and XPC service to run under two differentdebugserverprocesses (kernel_task > launchd > Xcode > lldb-rpc-server > debugserver).