Mysterious problem with [NSURL getResourceValue:forKey:error:]
Greetings, I've stumbled across a strange problem with [NSURL getResourceValue:forKey:error:] returning blank icons when executed within the context of a system daemon. I'm using this method to get the NSURLEffectiveIconKey (an NSImage) value for items. The process is launched by a privileged helper (installed a la EvenBetterAuthorizationSample). It's a SetUID executable, so its effective UID is the user (let's say 501) for which I want to get the icons (and other localized information). When the process is launched by the privielged helper, every NSImage returned by -getResourceValue:forKey:error: is a valid image, but the icons are all blank. When the process is launched from the shell (or via Xcode) as the regular user, the returned NSImages contain the correct icons. If the process is launched via sudo from the user's shell, the returned NSImages are also OK. I suspect that this is somehow a security or bootstrap context issue, but I'll defer to expert opinion on the cause or what can be done about it. James Bucanek
On 18 Aug 2016, at 05:21, James Bucanek <subscriber@gloaming.com> wrote:
The process is launched by a privileged helper (installed a la EvenBetterAuthorizationSample).
Let’s see if I understand this properly: * You have a daemon, A, running as root. * That daemon fork/exec's a helper tool, B. * B switches its effective user ID to that of some user. * B has mysterious problems. If this is accurate, it’s not a huge surprise. B is running in a parlous environment, because half of its context has been switched to that of the user but half of its environment has been inherited from the daemon. It’s not uncommon for weird problems to crop up in that case. For example, have fun accessing the keychain from B (-: Is B running as a role account user? Or an actual user? Share and Enjoy -- Quinn "The Eskimo!" <http://www.apple.com/developer/> Apple Developer Relations, Developer Technical Support, Core OS/Hardware
Quinn "The Eskimo!" wrote:
Let’s see if I understand this properly:
* You have a daemon, A, running as root.
* That daemon fork/exec's a helper tool, B.
* B switches its effective user ID to that of some user.
Just to be clear, B is a SetUID executable, so it's effective UID is set during launch ... but yes, the code is running with UID=0, EUID=501.
* B has mysterious problems.
If this is accurate, it’s not a huge surprise. B is running in a parlous environment, because half of its context has been switched to that of the user but half of its environment has been inherited from the daemon. It’s not uncommon for weird problems to crop up in that case. For example, have fun accessing the keychain from B (-:
That's kind of what I thought, but I figured it didn't hurt to ask. And yes, I've already run into the keychain issue. :(
Is B running as a role account user? Or an actual user?
It's a real user. The sole purpose of this process is to collect user-perspective metadata for filesystem items (custom icon, localized display name, etc.) on behalf of a second process running as root. Question: I now how a second system daemon (which I created to support XPC communications). This one is a bit special because its launchd properties include the <key>UserName</key> so the process is executes as the user that installed it. Does the UserName key "do the right thing" and set up a system daemon's environment/context as the user, thus giving me the context I'm looking for? James
On 18 Aug 2016, at 17:58, James Bucanek <subscriber@gloaming.com> wrote:
It's a real user. The sole purpose of this process is to collect user-perspective metadata for filesystem items (custom icon, localized display name, etc.) on behalf of a second process running as root.
In that case you need to make sure that this code is started in the correct user context; there’s no supported way to move code between contexts [1]. I have suggestions for how to start your code in the right context but I want to address the following first.
Question: I now [have] a second system daemon (which I created to support XPC communications). This one is a bit special because its launchd properties include the <key>UserName</key> so the process is executes as the user that installed it.
OK, that statement is either incorrect or illustrates that we’re not using the same terminology. A *launchd daemon* is system wide. If you include the `UserName` property it’s started as that user; the only legitimate use case here is to supply a role account. If you don’t include the `UserName` property, the daemon runs as root. A *launch agent* is per user. It runs as the user in whose context the agent was loaded. If you include the `UserName` property, it’s ignored. Does that make sense? If so, is this “second system daemon” actually an agent? This is important because, in order to guarantee that you can act like the user, your code /must/ be running as an agent. So a lot of the time you need two launchd jobs, one daemon providing system-wide functionality and one agent, loaded into each user’s context, that performs actions that can only be done in that context. Share and Enjoy -- Quinn "The Eskimo!" <http://www.apple.com/developer/> Apple Developer Relations, Developer Technical Support, Core OS/Hardware [1] There’s two problems with doing this: * The set of context information is not documented to be exhaustive, so new stuff could be added in the future. * The security context is now (since 10.7) tied to the audit session ID, and there’s no public API for switching that.
Quinn "The Eskimo!" <mailto:eskimo1@apple.com> August 19, 2016 at 1:42 AM On 18 Aug 2016, at 17:58, James Bucanek<subscriber@gloaming.com> wrote:
Question: I now [have] a second system daemon (which I created to support XPC communications). This one is a bit special because its launchd properties include the<key>UserName</key> so the process is executes as the user that installed it.
OK, that statement is either incorrect or illustrates that we're not using the same terminology. A *launchd daemon* is system wide. If you include the `UserName` property it's started as that user; the only legitimate use case here is to supply a role account. If you don't include the `UserName` property, the daemon runs as root.
A *launch agent* is per user. It runs as the user in whose context the agent was loaded. If you include the `UserName` property, it's ignored.
Does that make sense? If so, is this "second system daemon" actually an agent?
It makes prefect sense. If you have an interest in understanding what I'm doing, I'll summarize it here. But you can skip this explanation as I think I've already found a solution. I've created a service, aptly named the "switchboard," that I use to allow my various processes (agents, daemons, client apps, helpers, etc.) to form ad hoc XPC connections with each other. They do that by exchanging their NSXPCListenerEndpoint objects via the switchboard daemon. The switchboard has to be in a Mach namespace that all processes (both user and root) from all sessions (system, user, and gui) can connect with via [NSXPCConnection initWithMachServiceName:...]. However, each switchboard service only serves a single user, so I really don't want it running as root or be accessible from just any process. My solution is to install the switchboard as a system daemon (/Library/LaunchDaemons), so it's globally reachable. To tighten security, the daemon uses the <key>UserName</key> property so the daemon runs as that particular user, and the service rejects any XPC connections from a process with an effective UID of an unknown user. So far, this seems to be working just great.
This is important because, in order to guarantee that you can act like the user, your code /must/ be running as an agent. So a lot of the time you need two launchd jobs, one daemon providing system-wide functionality and one agent, loaded into each user's context, that performs actions that can only be done in that context.
That is clear to me now. I've found that I can successfully run my metadata gathering process from any process created in the "user" domain (Background session) or "gui" domain (Aqua session). But everything that was launched/exec'd/spawned from the system domain doesn't work, no matter what its UID or EUID is. My solution is to leverage a user agent that runs in either the user's "user" or "gui" domain, and use the switchboard to let my root process form a connection with it.
[1] There's two problems with doing this:
* The set of context information is not documented to be exhaustive, so new stuff could be added in the future.
* The security context is now (since 10.7) tied to the audit session ID, and there's no public API for switching that.
I have noticed the lack of a public API. ;) I did spend quit a bit of time looking for a way of either specifying the session ID for a new process, or switching sessions, but with a clearer understanding of the problem I think I now have a cleaner solution. Cheers, James
On 19 Aug 2016, at 18:30, James Bucanek <subscriber@gloaming.com> wrote:
My solution is to install the switchboard as a system daemon (/Library/LaunchDaemons), so it's globally reachable. To tighten security, the daemon uses the <key>UserName</key> property so the daemon runs as that particular user, and the service rejects any XPC connections from a process with an effective UID of an unknown user.
Its sounds like you’re well on the way to a solution but I’m curious about the above. In general the `UserName` property for a launchd daemon is for role accounts. Putting a user account there is weird because what user do specify? If there are two users simultaneously logged in (via fast user switching, or truly simultaneously via screen sharing), surely one of them has to miss out. Share and Enjoy -- Quinn "The Eskimo!" <http://www.apple.com/developer/> Apple Developer Relations, Developer Technical Support, Core OS/Hardware
participants (2)
-
James Bucanek
-
Quinn "The Eskimo!"