Impersonating user on IO thread causes render problems

116 views
Skip to first unread message

Phil Devaney

unread,
Mar 9, 2017, 11:10:27 PM3/9/17
to net-dev
I made the below post on the CEF forums (http://magpcss.org/ceforum/viewtopic.php?f=6&t=14971) and was pointed here. I'm not really sure if this is a networking problem, it could be a problem with the rendering stack.

Can anybody help with this or am I in completely the wrong place?

[Original post follows]

Our product has a client application running on Windows (WPF-based) that can display data from multiple services, with an integrated logon system that provides a single sign-on for all the services. Some of the services are intranet web apps using Negotiate/Kerberos authentication and we use CEF (via CefSharp for WPF integration) to display these.

Instead of using the default process credentials we want to use the credentials provided by the in-app logon UI (from a Win32 LogonUser call). To get this to work for the CEF-based components, we use CefPostTask to call Win32 ImpersonateLoggedOnUser on the TID_IO thread, then the code in HttpAuthSSPI picks up the impersonated credentials when it generates a Negotiate response header.

This was all working fine, until we found that it didn't work when the client is running on the same box as the server (this is quite a common scenario for some of our customers) and it would use the process credentials instead of impersonated. Experiments with a test app using .Net's HttpClient showed that a different value for the dwLogonType argument to LogonUser would fix this, but when we did this in our real app CEF started exhibiting major problems.

If we destroy and recreate the browser control, then the control doesn't render anything and is completely white. If we do a ReloadIgnoreCache() on the existing control, then initially it appears OK but if you move the mouse over the page then black rectangles will appear. If the window is resized then the control willl go mostly black with a few rectangles of content, mostly in the wrong position and overlapping. In both cases, if we use Fiddler we can see it does a GET /, recevies 401, then does another GET / with credentials and receives 200, but makes no further requests for scripts, images etc. Examing the response body shows the correct (impersonated) credentials were sent.

I can reproduce this easily in cefclient, I just modified the Reload button in root_window_win.cc to execute my LogonUser/CefPostTask calls with a hard-coded user/password:

// Add this to RootWindowWin::OnCommand under case IDC_NAV_RELOAD
HANDLE hToken;
if (::LogonUser(L"User", L"Domain", L"Password", LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken)) {
CefRefPtr<ImpersonateHelper> helper(new ImpersonateHelper(this));
CefPostTask(TID_IO, base::Bind(&ImpersonateHelper::Impersonate, helper, hToken));
}

// Add this class somewhere in root_window_win.cc
class ImpersonateHelper : public CefBase {
public:
ImpersonateHelper(CefRefPtr<RootWindowWin> window)
: window_(window) {
}

void Impersonate(HANDLE hToken) {
::ImpersonateLoggedOnUser(hToken);
::CloseHandle(hToken);

CefPostTask(TID_UI, base::Bind(&ImpersonateHelper::Reload, this));
}

private:
CefRefPtr<RootWindowWin> window_;

void Reload() {
if (CefRefPtr<CefBrowser> browser = window_->GetBrowser())
browser->ReloadIgnoreCache();
}

IMPLEMENT_REFCOUNTING(ImpersonateHelper)
};

Note you don't have to actually load a website that uses authentication, any website even google.com will show the rendering problems after the Impersonate call. If LOGON32_LOGON_INTERACTIVE is changed to LOGON32_LOGON_NEW_CREDENTIALS then the problem doesn't manifest (but impersonation doesn't always work as described above).

Any suggestions on how to proceed with this would be greatly appreciated. I've tried running cefclient under App Verifier to look for heap corruption or other problems, but then CEF fails to display the website at all. There are no exceptions in the debugger output window, and I can't see anything obvious in the debug.log.


Ryan Sleevi

unread,
Mar 10, 2017, 9:45:36 AM3/10/17
to Phil Devaney, net-dev
On Thu, Mar 9, 2017 at 11:10 PM, Phil Devaney <phild...@gmail.com> wrote:

Any suggestions on how to proceed with this would be greatly appreciated. I've tried running cefclient under App Verifier to look for heap corruption or other problems, but then CEF fails to display the website at all. There are no exceptions in the debugger output window, and I can't see anything obvious in the debug.log.

Unfortunately, we can only provide support for Chromium code in general. The CEF Forums are a better place for this sort of degree of customization, because Chromium architecture doesn't do that degree of impersonation.

With that said, the short answer I would give you is this: Don't impersonate across task boundaries. Because tasks may be reordered, you end up 'leaking' the impersonated authority in the thread for all tasks that may need to execute in your posting.

That means that if you have a sequence of events that spans a series of tasks, you need to impersonate prior to starting each of those tasks, and then drop impersonation prior to the end of each tasks. You must not allow the impersonation beyond that, or, as you'll see, bad things will happen. For example, you CefPostTask the impersonation, then CefPostTask to the ::Reload - that's a dangerous pattern that will not be safe.

Phil Devaney

unread,
Mar 13, 2017, 12:20:40 AM3/13/17
to net-dev, phild...@gmail.com, rsl...@chromium.org

Thanks for your reply.

We actually don't do the Reload call in our real application, instead we don't even create the browser instance until after logon. Even if I remove the Reload from my sample the problem still exists.

I realise this is not an ideal pattern but we actually want the impersonation to persist across tasks - when the user logs on to our application we want all Negotiate requests to use the impersonated credentials until they log off again. The other option is to use the CefRequestHandler.GetAuthCredentials callback, but this would require keeping the user's password in memory for the whole of their logon session so was rejected.

I have logged a support ticket with MS about the difference in behaviour of LogonUser, so hopefully we can find a resolution that means we don't use the argument that causes this issue.

Ryan Sleevi

unread,
Mar 13, 2017, 9:38:10 AM3/13/17
to Phil Devaney, net-dev, Ryan Sleevi
On Mon, Mar 13, 2017 at 12:20 AM, Phil Devaney <phild...@gmail.com> wrote:
I realise this is not an ideal pattern but we actually want the impersonation to persist across tasks - when the user logs on to our application we want all Negotiate requests to use the impersonated credentials until they log off again. The other option is to use the CefRequestHandler.GetAuthCredentials callback, but this would require keeping the user's password in memory for the whole of their logon session so was rejected.

Not only is it not an ideal pattern, it's a pattern which Chromium code will explicitly break in the future, as we move towards a TaskScheduler world.

To avoid that - and security issues in your product that can emerge from improper authentication tokens - you should consider alternative designs. There's no future guarantee of thread affinity, and the existing process which allows for the reordering of tasks (e.g. because of PostDelayedTask) make this an unsafe and insecure pattern. 

Phil Devaney

unread,
Mar 14, 2017, 12:17:07 AM3/14/17
to net-dev, rsl...@chromium.org
Thanks, I have passed this info on to our architects & PMs. We will look into a way to use the password entered in our logon UI in the CEF GetAuthCredentials callback.

I think I understand what is causing the problems with rendering. I suspect when the named pipes etc are created for IPC they are given a security descriptor, and when we impersonate they can no longer be accessed and the IPC to the render process stops working. When we use the NEW_CREDENTIALS option, this only affects high-level APIs that create network connections like InitializeSecurityContent, not things like sockets or non-network things like pipes.
Reply all
Reply to author
Forward
0 new messages