WindowsAppSDK
WindowsAppSDK copied to clipboard
CredUIPromptForWindowsCredentials crashes in unpackaged app
Describe the bug
Please see the attached solution, there are three projects, one packaged WASDK, one unpackaged WASDK and one WinForms project. All three projects use the same CredentialService file/class to display the credential ui. The unpackaged app crashes here:
Not Flagged 29672 1 Main Thread Main Thread UnpackagedWASDK.dll!TestCredPicker.CredentialService.NativeMethods.CredUIPromptForWindowsCredentials
Not Flagged 60556 4 Worker Thread .NET Counter Poller System.Private.CoreLib.dll!System.Threading.WaitHandle.WaitOneNoCheck
Not Flagged 31620 5 Worker Thread .NET TP Worker System.Private.CoreLib.dll!Interop.Kernel32.GetQueuedCompletionStatus
Not Flagged 18440 6 Worker Thread .NET TP Gate System.Private.CoreLib.dll!System.Threading.WaitHandle.WaitOneNoCheck
Not Flagged 31444 7 Worker Thread .NET TP Worker System.Private.CoreLib.dll!Interop.Kernel32.GetQueuedCompletionStatus
Not Flagged 62060 8 Worker Thread .NET ThreadPool IO System.Private.CoreLib.dll!System.Threading.PortableThreadPool.IOCompletionPoller.Poll
Not Flagged 67480 9 Worker Thread .NET TP Worker System.Private.CoreLib.dll!Interop.Kernel32.GetQueuedCompletionStatus
Not Flagged 39756 10 Worker Thread .NET TP Worker System.Private.CoreLib.dll!Interop.Kernel32.GetQueuedCompletionStatus
Not Flagged > 57496 0 Worker Thread <No Name> UnpackagedWASDK.dll!UnpackagedWASDK.App.InitializeComponent.AnonymousMethod__4_2
UnpackagedWASDK.dll!UnpackagedWASDK.App.InitializeComponent.AnonymousMethod__4_2(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) Line 75 C#
Microsoft.WinUI.dll!WinRT._EventSource_global__Microsoft_UI_Xaml_UnhandledExceptionEventHandler.EventState.GetEventInvoke.AnonymousMethod__1_0(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) Unknown
Microsoft.WinUI.dll!ABI.Microsoft.UI.Xaml.UnhandledExceptionEventHandler.Do_Abi_Invoke(nint thisPtr, nint sender, nint e) Unknown
Steps to reproduce the bug
- See the attached sample app: TestCredPicker.zip
- Should display the credential dialog for the current logged on user:
Expected behavior
I would expect that either all projects crash, because there may be something wrong with my PInvoke code, or none. But as both packaged and WinForms project run without problems, i suspect there is something wrong with the unpackaged nature of the project.
Screenshots
No response
NuGet package version
Windows App SDK 1.5.2: 1.5.240404000
Packaging type
Unpackaged
Windows version
Windows 10 version 22H2 (19045, 2022 Update)
IDE
Visual Studio 2022
Additional context
I don't know what version of WASDK introduced this problem, because at some point in time this code was developed and working with WASD (1.2.221116.1).
There is a working WinForms project included which does not use WinUI or WASDK.
This seems to work without issue on Win11.
I guess the other projects work too on your system using Win11? Can anyone confirm that Win10 is the culprit?
I guess the other projects work too on your system using Win11?
Yes.
Thinking about it for a little, have you tried referencing the Windows App SDK in a WinForms or WPF application and seeing if you have the same problem? Also, you have the Windows App SDK set as self contained in the unpackaged project, do you have different results if it is framework dependent? I ask this because that is a difference between the packaged and unpackaged projects.
I did check with true and false for WindowsAppSDKSelfContained but that did not make a difference.
What do you mean by referencing? Addin a ProjectReference to WASDK and using some random component? Isn't there a whole lot of stuff going on behind the scenes which the WASDK template sets up?
I mean simply adding the NuGet package to a project and setting WindowsPackageType to None and maybe setting the Windows App SDK self contained setting too. The stuff that goes on in the templates is irrelevant, this is all about testing whether the presence of the Windows App SDK in the project or process is enough to cause problems.
I tried your suggestion, even getting Microsoft.Windows.SDK.NET.dll loaded into the process like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19044.0</TargetPlatformMinVersion>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<UseRidGraph>true</UseRidGraph>
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240404000" />
</ItemGroup>
</Project>
public Form1()
{
InitializeComponent();
try
{
Microsoft.UI.Xaml.Window window = new Microsoft.UI.Xaml.Window();
}
catch (Exception)
{
}
CredentialService credentialService = new CredentialService();
credentialService.PromptForCurrentCredentials(this.Handle, "Credentials", "Please enter");
}
"Unfortunately" this does not crash on my machine.
Hi @Balkoth
your sample its working on Win11.
It looks like you are using p/invoke to open the dialog. You can use P/Invoke for Windows Form and for WinUI 3, you can use Windows.Security.Credentials which is exist in winui.
this is my Class which is using Windows.Security.Credentials and its working in WinUI 3 Packaged/UnPackaged
using Windows.Security.Credentials.UI;
using Windows.Security.Credentials;
namespace WinUICommunity;
public static class CredentialHelper
{
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetComputerName(StringBuilder lpBuffer, ref uint nSize);
private static async Task<CredentialPickerResults> PickCredentialBase(string caption, string message, CredentialSaveOption credentialSaveOption, AuthenticationProtocol authenticationProtocol, bool alwaysDisplayDialog)
{
uint nSize = 260;
StringBuilder sbComputerName = new StringBuilder((int)nSize);
GetComputerName(sbComputerName, ref nSize);
var options = new CredentialPickerOptions()
{
TargetName = sbComputerName.ToString(),
Caption = caption,
Message = message,
CredentialSaveOption = credentialSaveOption,
AuthenticationProtocol = authenticationProtocol,
AlwaysDisplayDialog = alwaysDisplayDialog
};
return await CredentialPicker.PickAsync(options);
}
public static async Task<CredentialPickerResults> PickCredential(string caption, string message, CredentialSaveOption credentialSaveOption, AuthenticationProtocol authenticationProtocol, bool alwaysDisplayDialog)
{
return await PickCredentialBase(caption, message, credentialSaveOption, authenticationProtocol, alwaysDisplayDialog);
}
public static async Task<CredentialPickerResults> PickCredential(string caption, string message, CredentialSaveOption credentialSaveOption, AuthenticationProtocol authenticationProtocol)
{
return await PickCredentialBase(caption, message, credentialSaveOption, authenticationProtocol, true);
}
public static async Task<CredentialPickerResults> PickCredential(string caption, string message, CredentialSaveOption credentialSaveOption)
{
return await PickCredentialBase(caption, message, credentialSaveOption, AuthenticationProtocol.Basic, true);
}
public static async Task<CredentialPickerResults> PickCredential(string caption, string message)
{
return await PickCredentialBase(caption, message, CredentialSaveOption.Selected, AuthenticationProtocol.Basic, true);
}
public static PasswordCredential GetPasswordCredential(string resource, string username)
{
if (string.IsNullOrEmpty(resource) || string.IsNullOrEmpty(username))
{
return null;
}
try
{
var vault = new PasswordVault();
return vault.Retrieve(resource, username);
}
catch (Exception)
{
}
return null;
}
public static void AddPasswordCredential(string resource, string username, string password)
{
if (string.IsNullOrEmpty(resource) || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
return;
}
var vault = new PasswordVault();
var credential = new PasswordCredential(resource, username, password);
vault.Add(credential);
}
public static void RemovePasswordCredential(string resource, string username)
{
if (string.IsNullOrEmpty(resource) || string.IsNullOrEmpty(username))
{
return;
}
var vault = new PasswordVault();
var credential = vault.Retrieve(resource, username);
vault.Remove(credential);
}
public static async Task<bool> RequestWindowsPIN(string message)
{
// Check if Windows Hello is available
if (await UserConsentVerifier.CheckAvailabilityAsync() != UserConsentVerifierAvailability.Available)
{
return false;
}
var consentResult = await UserConsentVerifier.RequestVerificationAsync(message);
if (consentResult == UserConsentVerificationResult.Verified)
{
// Windows Hello PIN was successfully verified
return true;
}
else
{
// Verification failed or was canceled
return false;
}
}
}
Thanks but your sample has the one big problem why i have to use PInvoke: Your sample uses string for the password and therefore the password is in cleartext in memory until the app gets shutdown.
So i would prefer if someone from the team can check this on win10.
"Unfortunately" this does not crash on my machine.
Yes, this is unfortunate. You ruled out it being a platform invoke problem because it should be crashing in the packaged WinUI 3 application and the WPF/WinForms application if it was a platform invoke issue. This unfortunate result actually provides the same logic for the Xaml runtime and the Windows App Runtime. If it was the Xaml runtime, then the packaged application version should be crashing, if it was the bootstrapper or registration free WinRT functionality then the WPF/WinForms application should be crashing.
One easy way to get a definitive answer would be to make an unpackaged C++ WinUI application and directly call this function or, to try to mimic the call portion of platform invoke, use runtime dynamic linking to call it (aka, LoadLibrary/GetProcAddress).
On my Windows 10 OS (22H2), it works for me with maxversiontested in the Manifest file, otherwise I get "Invalid pointer"
<maxversiontested Id="10.0.19041.0"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
Starts working when i put <maxversiontested Id="10.0.19044.0"/> into the manifest, so who is to blame now?
Is anyone from the team looking into this or does the maxversiontested hack confirm this is a non-issue from WASDK?
This is incredibly frustrating, because this problem is affecting software in production.
Why does no one on the team care for this issue? What is missing here, that is holding you back, to investigate and acknowledge or deny that this is a WASDK issue?
Can you share either the output-log dump of CredUIPromptForWindowsCredentials, or enable first-chance exceptions to see what this API itself is returning? This is a Windows Platform API, not directly part of Windows App SDK.
@ghost1372 - the Windows.Security.Credentials API is part of Windows itself, not WinUI (or WinUI3), for clarity. See https://learn.microsoft.com/samples/microsoft/windows-universal-samples/credentialpicker/ as well.
FWIW, I put together a little WPF app using the CredentialPicker above, like this:
private async void Clickery_Click(object sender, RoutedEventArgs e)
{
// Show a credential picker to acquire the user's domain credentials.
var credPicker = new CredentialPickerOptions
{
Message = "Sign in",
Caption = "Windows Credential Picker",
TargetName = "Windows Authentication",
AuthenticationProtocol = AuthenticationProtocol.Negotiate,
CredentialSaveOption = CredentialSaveOption.Hidden,
CallerSavesCredential = false,
};
var credResult = await CredentialPicker.PickAsync(credPicker);
Clickery.Content = credResult.ErrorCode == 0 ? "Success" : "Failure";
}
... which ran and showed the Windows Hello prompt to get me to sign in. The resulting credResult.Credential contains a valid credential buffer.
I'll try with an unpackaged WASDK WinUI3 app, but my hunch is it'll work there as well. I've asked some folks internally to see if there's known problems with this Win32 API.
@Balkoth - I'm able to use the Windows credential picker API from WPF, WinUI3, unpackaged apps. See https://github.com/jonwis/jonwis.github.io/tree/user/jonwis/credpicker/code/CredPickerSampleWinUI3/CredPickerSampleWinUI3 for the details. Can you try it out and see? That'd let you avoid all the hand-rolled p/invoke and just use our WinRT API call support.
The one drawback is that while CredUIPrompt... lets you specify an HWND as the parent for the prompt, the CredentialPicker does not; for Win32 apps the UX gets parented to the desktop.
I've filed http://task.ms/50726835 for the team to add a Windows.UI.WindowId ParentWindow; property to the picker options object to fix that.
Thanks but your sample has the one big problem why i have to use PInvoke: Your sample uses string for the password and therefore the password is in cleartext in memory until the app gets shutdown.
This...
Thanks but your sample has the one big problem why i have to use PInvoke: Your sample uses string for the password and therefore the password is in cleartext in memory until the app gets shutdown.
This...
Just to ask, in what way is the credential in clear text in memory until the application shuts down? Why do you believe that the CredentialPickerResults actually stores any passwords in clear text and doesn't use secure means to clear the memory when you release all references to the object? These are serious questions. If you have major evidence of this, the credential picker has to be reported as a major security flaw.
Because the password is retrieved in a string object and strings live forever.
I would be extremely surprised if that is true. Sure, C# strings are immutable, but that only means that the string object is read only while it is alive. This doesn't mean that a C# string will not be reclaimed by the GC. But I am hardly an expert on C#, so I'll have to leave it to an expert to give a definitive comment on that aspect.
If i remember correctly the runtime itself decides whether to intern strings and therefore keep them forever. Either way, using string for the password is a non deterministic approach on if and when cofidential data gets unloaded from memory.
FWIW, the .NET folks have this statement on SecureString - https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md - specifically, that it provides minimal actual value, and should not be used in new designs. I've asked the Windows Security team to chime in on this thread for a definitive statement. See also https://devblogs.microsoft.com/oldnewthing/20130529-00/?p=4223 ... note that the point of SecureZeroMemory is to ensure the optimizer won't discard a call to memset on storage that's about to be freed.
However, under the covers, it turns out that CredentialPickerResults stores the user name and password values in an HSTRING. When the -Result object is destroyed (the last reference on it is released, including those from GC objects) it calls SecureZeroMemory on the content of its stored HSTRING instances. Even if the HSTRINGs have been addref'd and given to somebody else to hold on to.
Strictly speaking, however, this violates the HSTRING contract which is that - like .NET String types - they are immutable. Someone with this code (in C++/WinRT) would be very sad:
winrt::hstring pw = (co_await CredentialPicker::PickAsync(...)).CredentialPassword();
UsePasswordToAuth(pw); // pw.data() is now non-null but points to all zeroes.
While in C# the ABI output of get_CredentialPassword is deep-copied into a System.String, in C++ (CX and WinRT and WRL) the output HSTRING is just used directly.
Re the GC and string lifetime and zeroing - correct, the GC allocates memory for the String from whatever store it has. If that's the OS heap, blocks are only zero'd when they're handed out, not on a call to free.
If i remember correctly the runtime itself decides whether to intern strings and therefore keep them forever.
The supplemental documentation for String.Intern paints a different picture. The runtime only automatically interns strings created from string literals, your code has to explicitly call Intern otherwise. There is also the CompilationRelaxationsAttribute attribute that can be applied to the assembly to force string interning off.
Can you share either the output-log dump of
CredUIPromptForWindowsCredentials, or enable first-chance exceptions to see what this API itself is returning? This is a Windows Platform API, not directly part of Windows App SDK.
The exception is ?caught? on this thread:
With this callstack:
While the main thread seems to wait:
What has happened to the investigation?
I don't know what to provide to get bugs like this fixed. It has a sample that reproduces the crash on the specified platform, yet no one from the team is willing to take the time to investigate the crash.