windowsdebuggingwindbgdebug-symbolswindows-driver

Provide symbol server auth to windows debugger


We have a tool that runs cdb and processes dmp files. The symbol server needs authentication for cdb to be able to download files from it. Our setup does not allow symbol access without auth.

I get the below log from the cdb for downloading ntdll. ntdll is just an example, and this dll could be any private dll.

The logs show its trying to auth using some credential provider, but there is no info about how to resolve the error. Can anyone share documentation related to credential provider, how to give cdb the credentials in non-interactive mode?

 
SYMSRV:  BYINDEX: 0x9
         c:\sym1*https://repos.net/factory/symbols/microsoftsymbols/
         ntdll.pdb
         03818EC49CBD48F2B0B378C3558734891
SYMSRV:  UNC: c:\sym1\ntdll.pdb\03818EC49CBD48F2B0B378C3558734891\ntdll.pdb - path not found
SYMSRV:  UNC: c:\sym1\ntdll.pdb\03818EC49CBD48F2B0B378C3558734891\ntdll.pd_ - path not found
SYMSRV:  UNC: c:\sym1\ntdll.pdb\03818EC49CBD48F2B0B378C3558734891\file.ptr - path not found
SYMSRV:  HTTPGET: /factory/symbols/microsoftsymbols//ntdll.pdb/03818EC49CBD48F2B0B378C3558734891/ntdll.pdb
SYMSRV:  HttpQueryInfo: 80190191 - HTTP_STATUS_DENIED
SYMSRV:  Credential Provider: Getting credentials for: protocol=https, host=repos.net, path=factory/symbols/microsoftsymbols/, interactive=0, isRetry=0
SYMSRV:  Credential Provider: Getting credentials from provider: C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\credentialproviders\gcmw\dbgcredentialprovider_gcmw.dll...
SYMSRV:  Credential Provider: DebuggerCredentialManager.exe is not found.
SYMSRV:  Credential Provider: DbgCredentialProvider_gcmw: Failed to build execution command.
HttpQueryInfo: Requesting Authentication for Web Site.
SYMSRV:  RESULT: 0x80190191

Update 10/31/2024:

Modified the powershell script and its reading the credential from Credential manager successfully but the debugger is receiving only the last 13 chars. Verified the powershell script is returning the expected info. We use the debugger version 10.22621.755.

    SYMSRV:  BYINDEX: 0x7
         c:\sym1*https://repos.net/factory/symbols/microsoftsymbols/
         ntdll.pdb
         03818EC49CBD48F2B0B378C3558734891
SYMSRV:  UNC: c:\sym1\ntdll.pdb\03818EC49CBD48F2B0B378C3558734891\ntdll.pdb - path not found
SYMSRV:  UNC: c:\sym1\ntdll.pdb\03818EC49CBD48F2B0B378C3558734891\ntdll.pd_ - path not found
SYMSRV:  UNC: c:\sym1\ntdll.pdb\03818EC49CBD48F2B0B378C3558734891\file.ptr - path not found
SYMSRV:  HTTPGET: /factory/symbols/microsoftsymbols//ntdll.pdb/03818EC49CBD48F2B0B378C3558734891/ntdll.pdb
SYMSRV:  HttpQueryInfo: 80190191 - HTTP_STATUS_DENIED
SYMSRV:  Credential Provider: Getting credentials for: protocol=https, host=repos.net, path=factory/symbols/microsoftsymbols/, interactive=0, isRetry=0
SYMSRV:  Credential Provider: Getting credentials from provider: C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\credentialproviders\mycredentialprovider\mycredentialprovider.bat...
SYMSRV:  Credential Provider: Stream response:'WdG90amuHYswd'
SYMSRV:  Credential Provider: Stream response:''
SYMSRV:  Credential Provider: Failed to get user credentials from stream for 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\credentialproviders\mycredentialprovider\mycredentialprovider.bat'.
HttpQueryInfo: Requesting Authentication for Web Site.
SYMSRV:  RESULT: 0x80190191

credProviderLog.txt contains the Found PAT log:

Entering Credential Provider
interactive=0
symbolsRequest=1
isRetry=0
parentHwnd=0x10010
protocol=https
host=repos.net
path=factory/symbols/microsoftsymbols/
Installing module
Found path line: host=repos.net
Found symbol path: symbol:repos.net
Found PAT!

Solution

  • The documentation will be published in the near future. I've copied that documentation and pasted it below.

    Note that the specific location of DbgCredentialProvider.config.xml depends on which version of the Windows Kit you have installed. Based on your debugger logs, you don't have the latest Kit, but I don't think that affects the instructions below.

    The Windows debugger infrastructure has been updated to assist symbols or sources files download when the server returns
    an error HTTP_STATUS_DENIED 401 (access denied). In such case the request has to be resent with updated authentication headers
    containing proper credentials for the user.
    
    The bridge between the debugger and the credential providers is DbgCredentialProvider.dll. It will look for credential providers using the following algorithm:
    
        1) It opens a file DbgCredentialProvider.config.xml which should be located next to DbgCredentialProvider.dll. The search behavior that is used to locate
           DbgCredentialProvider.config.xml file may change in the future.
    
                The DbgCredentialProvider.config.xml file contains a folders list:
    
                     <?xml version="1.0" encoding="utf-8"?>
                     <!--
                     The config file is located next to DbgCredentialProvider.dll.
                     -->
                     <Settings>
                         <Folders>
                             <!--
                                 This is a list of the folders which should be provided as an absolute file path or
                                 relative to the location of this config file.
                             -->
                             <Folder>CredentialProviders</Folder>
                         </Folders>
                     </Settings>
    
                     You can list more than one folder under <Folders></Folders> elements. The folders can be relative or absolute path. If it is relative path, it is relative to the location
                     of the DbgCredentialProvider.config.xml file. The folders will be searched for providers in the order listed in the XML file.
    
                     In the current example the Folders collection has just one folder "CredentialProviders" and it is a relative path.
    
        2) Then all files with extension '*.xml' in CredentialProviders folder will be enumerated. The order in which the XML files are enumerated is unspecified. Those XML files describe which
           debugger credential providers are available for the debugger. You may find DbgCredentialProvider_gcmw.xml file (or other XML files) in CredentialProvider folder which contain data like this:
    
                     <?xml version="1.0" encoding="utf-8"?>
                     <CredentialProviders>
                         <!--
                               This is a list of the provider modules which should be provided as an absolute file path or
                               relative to the location of this config file.
                               The provider is a DLL, EXE or CMD file.
                         -->
                       <CredentialProvider ApiVersion="2.0.0">GCMW\DbgCredentialProvider_gcmw.dll</CredentialProvider>
    
                     </CredentialProviders>
    
           The XML file may list more than one credential provider. In this example we have just one provider DbgCredentialProvider_gcmw.dll and it is located
           in GCMW folder relative to the DbgCredentialProvider_gcmw.xml file location. The providers may be relative or absolute paths similar to the ones described
           in the section for DbgCredentialProvider.config.xml file.
    
    The debugger credential providers can be DLL, EXE or CMD script files. The debugger will ask every provider for credentials and it will use the credentials from the first provider
    which returns success.
    
    If the provider is implemented in a DLL it must export GetUserCredentials method as listed in this file.
    If the provider is implemented in EXE or a CMD script it should be able to process the following command line parameters (case insensitive), which cannot be combined with each other.
        - Get
        - Erase
        - Store
    
        Get Command
        It is used to retrieve a credential. The remaining data will be passed to the provider via the standard input stream:
    
            protocol=https or http. For security reasons we recommend you send credentials only over https.
            host=XXX ex. host=contoso.symbols.com
            path=YYY ex. path=apis/symbol/symsrv
            resourceKind=symbols or sources. Ex resourceKind=symbols
            isretry=true or false 
            issilent=true or false
            parenthwnd=ZZZ ex. parenthwnd=593598
            <Followed by an empty line to indicate the end of the input data>
    
            The full URI/URL is built by concatenating <protocol>://<host>/<path> from above i.e. it is
                    https://contoso.symbols.com/apis/symbol/symsrv
    
            The return data to the debugger is via the standard output stream as follows:
    
            username=xxx
            credentialkind=Basic
            password=yyy --> This can be a password or a PAT token
    
            Or 
    
            username=xxx
            credentialkind=Bearer
            header=Bearer <TOKEN_GOES_HERE> ---> Usually OAuth2 tokens begin with "ey" and it is a very long string
    
            Error may be returned via 
    
            error=zzzzz
    
            For example for the symbol server https://contoso.symbols.com/apis/symbol/symsrv the request would look like this:
    
            protocol=https
            host=contoso.symbols.com
            path=apis/symbol/symsrv
            resourceKind=symbols
            isretry=false 
            issilent=false
            parenthwnd=593598
            Followed by an empty line to indicate the end of the input data.
    
        Please note that the parameters above (or in the resulting output stream below) are not case sensitive.
    
        The debugger asks for the credential using isRetry=false. Some providers may be getting the token from their local cache. Once the debugger resends the HTTP request
        with this token the server may return 401 response again. This may be due to the token has expired. Then the debugger will ask the credential provider for a new token
        and this time the isRetry=true. In such case the provider should not use its cache but retrieve a brand new token.
    
        Some providers may display an authentication UI. If so it should use the 'parenthwnd' parameter so this UI would appear as a modal dialog to the main debugger window.
        Otherwise the authentication UI may be hidden behind the main debugger window and the user may be given the impression that the debugger is "frozen".
    
        A debugger client application similar to windbg may use may use DBG_CREDENTIAL_PROVIDER_PARENT_HWND environment variable or imagehlp/dbghelp SymSetParentWindow method to setup the parent HWND.
        You can also use IDebugAdvanced2::Request message DEBUG_REQUEST_SET_PARENT_HWND with value of HWND cast to UINT32.
    
        In some non-interactive environments there may be no user to interact with a UI. In such a case the parameter issilent would be true.
        The provider should not be displaying any authentication or other UI when this parameter is true.
    
        How to setup the silent (non interactive) environment is described below:
               a) SYMOPT_NO_PROMPTS described in this document https://learn.microsoft.com/windows-hardware/drivers/debugger/symbol-options.
               b) SSRVOPT_UNATTENDED described in this document https://learn.microsoft.com/previous-versions/ff797954(v=vs.85)
               c) IDebugSymbols::SetSymbolOptions described in this document https://learn.microsoft.com/windows-hardware/drivers/ddi/dbgeng/nf-dbgeng-idebugsymbols3-setsymboloptions
               d) From the Windows debugger you can execute the following bang command "!sym quiet" (or "!sym prompts off")
    
        Store Command
            The credential provider may choose to use this command to store the credentials into its cache. The input parameters are same as for Get command.
            There is no output return value needed (Error still might be returned)
    
        Erase Command
            The credential provider may choose to use this command to erase the credentials from its cache. The input parameters are same as for Get command.
            There is no output return value needed (Error still might be returned)
    
        Here is an example of a CMD file which returns an HTTP authentication header:
    
             OAuth2CredentialProvider.xml file located in CredentialProviders folder:
    
                 <?xml version="1.0" encoding="utf-8"?>
                 <CredentialProviders>
                     <CredentialProvider ApiVersion="2.0.0" >OAuth2CredentialProvider\OAuth2CredentialProvider.cmd</CredentialProvider>
                 </CredentialProviders> 
    
             OAuth2CredentialProvider.cmd file located in OAuth2CredentialProvider folder:
    
                 @echo off
                 echo username=UserName@domain.com
                 echo header=Bearer <TOKEN_GOES_HERE>
    
       Here is a PS script example of returning a PAT token:
    
             File PatCredentialProvider.xml content:
    
                 <?xml version="1.0" encoding="utf-8"?>
                 <CredentialProviders>
                     <CredentialProvider ApiVersion="2.0.0">PATCredentialProvider\PATCredentialProvider.bat</CredentialProvider>
                 </CredentialProviders>
    
             File PATCredentialProvider.bat located in PATCredentialProvider folder:
    
                 @echo off
                 <PATH_TO_POWERSHELL>\PowerShell.exe -NoProfile -executionpolicy Unrestricted -WindowStyle Hidden -File "%~dp0\PATCredentialProvider.ps1"
    
             File PATCredentialProvider.ps1 located in PATCredentialProvider folder:
    
                 <#
                .SYNOPSIS
                   Given input, parses to find out which symbol server we want credentails for, and searches the Microsoft Credential Manager for those credentials.
                   If found, prints the credentials to standard output. If not, prints error.
    
                .INPUT
                   Delivered through standard input:
                   protocol=http or https
                   host=xxx ex. host=contoso.symbols.com
                   path=yyy ex. path=apis/symbol/symsrv
                   resourceKind=symbols
                   isretry=false 
                   issilent=false
                   parenthwnd=593598
                   <empty line to mark the end of the input parameters>
    
                .OUTPUT
    
                   Delivered through standard output:
    
                   username=aaa
                   password=bbb - where the password can be a password or PAT. When PAT is returned the username will be any name (not necessarily the name of the currently logged in user)<!--[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="It's an example")]-->
                   <empty line to mark the end of the output parameters>
    
                #>
    
                $logDirectory = (Get-Item Env:LoggingDirectory).Value
                $logFile = Join-Path $logDirectory "credProviderLog.txt"
    
                try
                {
                   "Entering Credential Provider" | Out-File $logFile -Append
    
                   $lines = While($line=Read-Host) {$line}
                   $lines | Out-File $logFile -Append
                   if (!(Get-Module "CredentialManager"))
                   {
                       "Installing module" | Out-File $logFile -Append
    
                       Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
    
                       Install-Module CredentialManager -force -Scope CurrentUser
                   }
    
                   $pathLine = $lines | Where-Object {$_.StartsWith("path=")} | Select-Object -First 1
                   "Found path line: $pathLine" | Out-File $logFile -Append
    
                   [regex]$regex="path=(?<ServerName>.*)"
                   $pathLine -Match $regex
    
                   $symbolPath = "symbol:$($Matches.ServerName)"
    
                   "Found symbol path: $symbolPath" | Out-File $logFile -Append
    
                   $PAT = Get-StoredCredential -Target $symbolPath -AsCredentialObject
    
                   if ($PAT)
                   {
                       "Found PAT!" | Out-File $logFile -Append
                       Write-Host "username=placeholder"
                       Write-Host "password=$($PAT.Password)"; # For OAuth 2 tokens You can change to output header=Bearer TOKEN
                       Write-Host
                   }
                   else
                   {
                       "Could not locate PAT for Symbol Server: $symbolPath" | Out-File $logFile -Append
                       Write-Host "error=Could not locate PAT for Symbol Server: $symbolPath"
                   }
                }
                catch [System.SystemException]
                {
                   "ERROR" | Out-File $logFile -Append
                
                   $_ | Out-File $logFile -Append
                }
    
    If you are writing a custom provider located in a CMD or EXE file, you can test it simply by launching it from a console window using the commands.
    For example:
    
    DebuggerCredentialManager.exe get
    
        This would start the application, and print something like this and then will wait for user input (an empty line indicates end of the user input).
    
    [Information] [DebuggerCredentialProvider.102949]Microsoft Debugger Credential Manager version 2024.0409.02656.285 (Windows, .NET 6.0.29) 'get'
    
    Please note that the providers may chose to print some diagnostic information on the output stream, but the debugger would ignore it, nor it will
    display those to the user. So the examples of extra information printed here are for illustration purposes only. Other providers may print
    other diagnostic information or not print anything.
    
    Here is the information you enter (it can be any text case) in the console window (input stream):
    
    protocol=https
    host=contoso.symbols.com
    path=apis/symbol/symsrv
    resourceKind=symbols
    isretry=false 
    issilent=false
    parenthwnd=593598
    Then press ENTER key twice to indicate end of user input.
    
    The provider responds via the standard output stream.
    The debugger will ignore any lines not matching the pattern `key=value` where key is
    one of the following: protocol, host, path, username, credentialkind,or header.
    Case is ignored in the key. The debugger treats a blank line as the end of input.
    
    [Verbose] [DebuggerCredentialProvider.103258]AzureCredentialProvider - Attempting to acquire bearer token using provider 'Msal Cache'
    [Verbose] [DebuggerCredentialProvider.103300]Token expiration data - current UTC time:9/18/2024 5:33:00 PM, ExpiresOn: 9/18/2024 6:43:25 PM
    [Information] [DebuggerCredentialProvider.103300]AzureCredentialProvider - Acquired bearer token using 'Msal Cache'
    protocol=https
    host=contoso.symbols.com
    path=apis/symbol/symsrv
    username=UserName@domain.com
    credentialkind=Bearer
    header=Bearer eyJ0eXAi....
    <empty line>