-
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?
Add assessment for blessing from a sandboxed program #21
Conversation
| 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 |
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 the XPC_SERVICE key 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 different debugserver processes (kernel_task > launchd > Xcode > lldb-rpc-server > debugserver).
| } | ||
|
|
||
| guard CFGetTypeID(entitlement) == CFBooleanGetTypeID(), let boolValue = (entitlement as? Bool) else { | ||
| guard let boolValue = entitlement as? Bool else { |
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.
The explicit CFGetTypeID check wasn't necessary, because the as? already checks that:
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)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.
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 0/1 as false/true is probably more consistent with the rest of the system (which wouldn't have been written in Swift, and didn't expect a strong distinction between the two).
| 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" |
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.
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?
Hey Josh, it's been a while!
I had stopped working on my person project for a while, and I had given up on my goal to keep my main app sandboxed (while still having an unsandboxed XPC service to install an privileged helper, like Apple's EvenBetterAuthorizationSample).
My XPC service was attempting to call
SMJobBless(), always got denied, and I just couldn't figure out why. After coming back to with fresh eyes (for the third time!) and scouring the barrage of Console.app logs, I finally found this message:My XPC service was also sandboxed, unintentionally 🙃
For some reason I had
OTHER_CODE_SIGN_FLAGS = "--deep"set on my main app's Xcode target, which made its XPC services inherit thecom.apple.security.app-sandboxentitlement. Took ages, but I finally found the culprit.To save others the frustration, I'm contributing back this new assessment, to detect this condition. You already had an
isSandboxedextension (which isn't used elsewhere today), which worked perfectly here.