Validate user credentials and impersonate user

Working on a file server in c/c++ and need to do following:

  1. Validate user credentials (mac Username & password)
  2. Impersonate user security context in a thread running in a daemon, so that I can enumerate user's home directory and files/folders.

Regarding 2, found API: pthread_setugid_np - is this the right approach? If so, how do I verify user credentials and call this API?

Found this section in TN2083:

Does this mean that its really not possible to impersonate user and access their home directory etc if the user isn't logged in via terminal/console? or if they have FileVault enabled?

Answered by DTS Engineer in 802683022

Does this mean that its really not possible to impersonate user and access their home directory etc if the user isn't logged in via terminal/console? or if they have FileVault enabled?

Yes, that is absolutely the case. As TN2083 laid out in a detailed example:

"It is not possible for a daemon to act on behalf of a user with 100% fidelity. While this might seem like a controversial statement, it's actually pretty easy to prove. For example, consider something as simple as accessing a preference file in the user's home directory. It's not possible for a daemon to reliably do this. If the user has an AFP home directory, or their home directory is protected by FileVault, the volume containing the home directory will only be mounted when the user is logged in. Moreover, it is not possible to mount the that volume without the user's security credentials (typically their password). So, if a daemon tries to get a user preference when the user is not logged in, it will fail."

Note that in both of these example, this is a FEATURE not a bug. The system was intentionally designed so that this data would not be accessible unless the user had logged in.

Regarding 2, found API: pthread_setugid_np - is this the right approach?

Right approach for what? Quoting TN2093 again:

"In some cases it is helpful to impersonate the user, at least as far as the permissions checking done by the BSD subsystem of the kernel."

pthread_setugid_np lets a process running as root shift one of it's thread identies to be a different user, at least as far as the BSD system is concerned. If you want to create a file as a different user, then it works great. If you want access to the users home directory... then it does nothing whatsoever. It changes how the system "thinks" about that particular thread but that doesn't change the larger state of other components, like the home directory not being available.

If so, how do I verify user credentials and call this API?

I'm not sure what you mean here. That API assumes you're running as a privileged user ("root"), so there isn't anything to verify.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Does this mean that its really not possible to impersonate user and access their home directory etc if the user isn't logged in via terminal/console? or if they have FileVault enabled?

Yes, that is absolutely the case. As TN2083 laid out in a detailed example:

"It is not possible for a daemon to act on behalf of a user with 100% fidelity. While this might seem like a controversial statement, it's actually pretty easy to prove. For example, consider something as simple as accessing a preference file in the user's home directory. It's not possible for a daemon to reliably do this. If the user has an AFP home directory, or their home directory is protected by FileVault, the volume containing the home directory will only be mounted when the user is logged in. Moreover, it is not possible to mount the that volume without the user's security credentials (typically their password). So, if a daemon tries to get a user preference when the user is not logged in, it will fail."

Note that in both of these example, this is a FEATURE not a bug. The system was intentionally designed so that this data would not be accessible unless the user had logged in.

Regarding 2, found API: pthread_setugid_np - is this the right approach?

Right approach for what? Quoting TN2093 again:

"In some cases it is helpful to impersonate the user, at least as far as the permissions checking done by the BSD subsystem of the kernel."

pthread_setugid_np lets a process running as root shift one of it's thread identies to be a different user, at least as far as the BSD system is concerned. If you want to create a file as a different user, then it works great. If you want access to the users home directory... then it does nothing whatsoever. It changes how the system "thinks" about that particular thread but that doesn't change the larger state of other components, like the home directory not being available.

If so, how do I verify user credentials and call this API?

I'm not sure what you mean here. That API assumes you're running as a privileged user ("root"), so there isn't anything to verify.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Note that in both of these example, this is a FEATURE not a bug. The system was intentionally designed so that this data would not be accessible unless the user had logged in.

On windows, its doable by calling LogonUser (using user's credentials), loading user's environment and then being able to access home directory etc. Is there an equivalent flow on macOS?

I'm not sure what you mean here. That API assumes you're running as a privileged user ("root"), so there isn't anything to verify.

That's the reason I'm getting user credentials as well. 1. That way I can verify that the client does have the right credentials to access the files. 2. I can pass on the credentials to logon the user and then access their files.

So what's the right approach for implementing a file server on macOS in which I can access files just as if the user was logged-on to the GUI terminal/console?

On windows, its doable by calling LogonUser (using user's credentials), loading user's environment and then being able to access home directory etc. Is there an equivalent flow on macOS?

No, nothing like that exists.

That's the reason I'm getting user credentials as well.

  1. That way I can verify that the client does have the right credentials to access the files.

This forum post shows how to validate their login/password using OpenDirectory. Note that this assume that the user has a password, which is not necessarily the case (for example, Smart Cards).

However...

  1. I can pass on the credentials to logon the user and then access their files.

There isn't any public API that will allow you to "log the user in". Note that, from a broader Unix perspective, the concept of "logging a user in so I can do something" isn't really how things like this work. A file server would normally authenticate the user and would then spawn a process (or modify a thread) that would then operate "as that user". In a pure Unix environment, there isn't really any difference between "a process running as a given user" and "a process running as a given user that was created in a login session".

So what's the right approach for implementing a file server on macOS in which I can access files just as if the user was logged-on to the GUI terminal/console?

You're asking for a solution to a problem that the system doesn't really try to solve, and least not in any broad/fundamental way. The basic approach I'd start here with is:

  • Run as a privileged (root) daemon so that BSD permissions aren't an issue.

  • (optional) Give that daemon Full Disk Access (FDA) to minimize sandbox/privacy restrictions.

  • Authenticate the user as above, then "operate" as that user for further actions. That could be done on a thread by thread basis, but I would actually spawn some kind of helper process as that user, then do all of that user's file system operations through that sub process. Actually, I'd probably move ALL activity over to that process, as I think that will give you the simplest implementation.

  • That process has access to whatever the system will let them access.

Starting with that foundation, this then becomes a question of which edge cases matter to you and how hard you're willing to work to address them. For example, TN2083 says that FileVault protected home directories are not accessible but I'm not sure that's actually true today. It was true of FileVault original implementation (which used DiskImages) but I don't know if that's been true since we moved to whole disk encryption through CoreStorage and then APFS.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for the detailed response! Authentication part I understood, so that's resolved.

In a pure Unix environment, there isn't really any difference between "a process running as a given user" and "a process running as a given user that was created in a login session".

This cleared up a lot of things, however, earlier you wrote this:

pthread_setugid_np lets a process running as root shift one of it's thread identies to be a different user, at least as far as the BSD system is concerned. If you want to create a file as a different user, then it works great. If you want access to the users home directory... then it does nothing whatsoever. It changes how the system "thinks" about that particular thread but that doesn't change the larger state of other components, like the home directory not being available.

Was this statement specific to pthread_setugid_np? If I'm reading this right, forking a new process won't have any limitation wrt accessing user home directory etc (excluding some edge cases) but using pthread-setugid_np would have limitation? or do these two basically have the same limitations?

My current file server on windows is setup to do impersonation at thread level, so simplest option for me to port code is doing thread level impersonation on macOS as well.

Should I go with pthread_setugid_np or is my only option forking a new process?

Accepted Answer

Was this statement specific to pthread_setugid_np? If I'm reading this right, forking a new process won't have any limitation wrt accessing user home directory etc (excluding some edge cases) but using pthread-setugid_np would have limitation? or do these two basically have the same limitations?

That's a tricky question to answer. In the "basic" sense, yes, I'd expect the two cases to behave the same. However, there are two factors that complicate that answer:

  1. There's a difference between "lying" and "being". The "fundamental" state here is your processes state (not your threads). An API like "pthread_setugid_np" works by changing what certain APIs return on that particular thread but the system is very complicated with different ways of answering the "same" question(s).

  2. Calling an API on one thread doesn't mean the code for that thread will actually run on that thread. Many of our APIs actually use multiple threads, particularly through GCD, which can make an API like pthread_setugid_np completely irrelevant.

How much much either of these will matter... is really hard to predict. The "basic" POSIX API set it basically thread bound, in which case I'd expect pthread_setugid_np to work fine. Our higher level APIs are far more complicated but that's only an issue if they happen to do a permission check on the "wrong" thread.

My current file server on windows is setup to do impersonation at thread level, so simplest option for me to port code is doing thread level impersonation on macOS as well.

Should I go with pthread_setugid_np or is my only option forking a new process?

Ultimately, that's a decision you'll need to make.

The "classic" Unix approach was to use "fork", but that was primarily because it was "easy". You'd write the implementation to handle a single session/transaction/session/etc., then use fork() to handle multiple users/sessions. Similarly, you'd write the implementation to run as "the user", allowed to do whatever "the user can do", then set the user it was run as at fork(). The only code that runs as root is the code that authenticates the user and creates session, minimize the risk of exploit(s) or the damage bugs can cause. More modern approaches like XPCService are just the natural extension/evolution of that architecture.

However, there is real value in code reuse and common architecture. The fact something should work better "in theory" doesn't really matter if the practical reality is that a full rewrite it going to create more problems and bugs than it solves.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks again for the detailed reply. I'll try out porting the existing code using thread-level impersonation and go from there.

Validate user credentials and impersonate user
 
 
Q