IIS 6 prefixing paths with \?


One of the most fundamental security changes in IIS6 appends \\?\ to all filesystem access by IIS. This is supposed to be fairly transparent, but…


Question:


Dear comrades:


I have a cgi executable program that has worked just fine under W2000 Server.  It also works fine when tested with Windows XP Pro.


Under W2003, the cgi executable works except when the cgi attempts to read a file from disk, please see the message reported below:


Cannot open file \\?\C:\Web\\Templates\DefaultTemplate.htm


Previously, the file path was not prefixed with \\?\ and everything worked as expected.  The cgi found the path and loaded the template above.  Can someone please shed some light as to what is now different under IIS 6?


Thank you,


Answer:


This looks like a bug in the CGI executable program exposed by IIS6.


\\?\ is a documented and existing syntax accepted by File IO API calls on Windows. Look up CreateFile and fopen on MSDN for more details.


For security reasons, IIS 6.0 uses \\?\ on all file system accesses (this includes serving of static files and loading of ISAPI DLLs and CreateProcess of CGI EXEs). \\?\ disables namespace canonicalization by the file system, which prevents a whole class of security issues related to canonicalization.


Since \\?\ is accepted by all File IO API calls on Windows, this change should transparently work downstream for any ISAPI DLL and CGI EXE.


A common failure pattern that I have seen happens when the custom ISAPI DLL or CGI EXE tries to interpret a filename and does so incorrectly ( for example, code that assumes that leading \\ means UNC share – this code will think that the UNC share server name is “?”, which causes problems). This failure is basically a bug in the custom binary.


Now, please realize that this change in IIS 6 to append \\?\ is by-design and cannot be changed/turned-off. The reason IIS 6 insists on this change is because of security. We knew that there will be a hit to application compatibility (i.e. exposing bugs in existing code, even if righteous, is not good compatibility), but the security gained from eliminating this canonicalization risk is worth the change.


See this other post for related possibilities.


//David

Comments (16)

  1. Alex says:

    Thank you very much for the detailed explanation.

    Best regards,

    Alex

  2. lexp says:

    If \? is valid synthax, why the following .net 2.0 code does not work:

    System.IO.File.ReadAllText(@"\?c:boot.ini");

  3. David.Wang says:

    Very good question.

    You must realize that \? is a Windows concept while .Net Framework and System.IO namespace are OS agnostic. This means that you cannot assume that .Net code on Windows allows \? .

    Two reasons:

    1. The .Net Framework is not tied to Windows nor Win32 API and its File IO namespace should be agnostic of the underlying platform. Since other file systems may not support \? , the .Net File IO API should not do anything "special" to make \? work.

    2. The .Net Framework runs on older Win9x OS which do not support \? syntax

    All of this means that if you want to write platform independent .Net code, you cannot explicitly use \? . The .Net Framework itself my internally use \? on appropriate Windows platforms for its own reasons, but from a conceptual standpoint for users of .Net, \? is not a valid concept.

    //David

  4. Rolf says:

    The Windows INI-File API calls seems to have problems with \? filenames too. In my CGI exe I did use some INI files which don’t anymore work under IIS6. Removing the \? from the Path does solve the problem.

  5. David.Wang says:

    Please give:

    1. an example of your exact INI file API call

    2. the exact strings you are using on the API call

    3. what makes you think the GetPrivate* API has problems with \?

    I believe the problem is that your code depends on file system canonicalization to function, and it should not.

    //David

  6. Bert Broeren says:

    Hello

    I have written a cgi program in vb .NET 2005

    when i run the program on an older server its oke

    on server 2003 and iis 6.0 i get an error with //?/

    do you have a sulotion

    bert

    see below

    The demand was for:

    <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

    version="1"

    Read="QUERY_STRING"/>

    The granted set of the failing assembly was:

    <PermissionSet class="System.Security.PermissionSet"

    version="1">

    <IPermission class="System.Security.Permissions.FileDialogPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

    version="1"

    Access="Open"/>

    <IPermission class="System.Security.Permissions.IsolatedStorageFilePermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

    version="1"

    Allowed="ApplicationIsolationByUser"

    UserQuota="512000"/>

    <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

    version="1"

    Flags="Execution"/>

    <IPermission class="System.Security.Permissions.UIPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

    version="1"

    Window="SafeTopLevelWindows"

    Clipboard="OwnClipboard"/>

    <IPermission class="System.Security.Permissions.UrlIdentityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

    version="1"

    Url="file://?/e:/cgi/linda_gis_net_sample.exe"/>

    <IPermission class="System.Security.Permissions.ZoneIdentityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

    version="1"

    Zone="Internet"/>

    <IPermission class="System.Drawing.Printing.PrintingPermission, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

    version="1"

    Level="SafePrinting"/>

    </PermissionSet>

    The assembly or AppDomain that failed was:

    linda_gis_net_sample, Version=1.0.2371.24102, Culture=neutral, PublicKeyToken=null

    The method that caused the failure was:

    Void Init_CGI()

    The Zone of the assembly that failed was:

    Internet

    The Url of the assembly that failed was:

    file://?/e:/cgi/linda_gis_net_sample.exe

  7. David.Wang says:

    Bert – Your error references a missing permission of the Internet Zone to read from the local Environment, so it looks like a problem in the configuration of your CGI within the .Net Framework on the new server. I suggest looking up documentation for CAS Policy on .Net.

    //David

  8. Sean says:

    Thanks for the article, I think it explains a problem I’m having with a cgi executable that doesn’t run when moved from IIS 5 to IIS 6. It returns an error to the browser

    "Cannot find  \?C:Inetpubwwwrootappnamecgi-binapp.ini". Would I be right in thinking that I’d have to get an updated version of the application from the original vendor (purchased application, no source code available), or is there anything I might be able to do to get it to work on IIS 6?

  9. mikey123 says:

    Does this submission form work?

  10. mikey123 says:

    If a CGI .EXE program invokes a COM visible .NET DLL which executes, for example: ConnectionStringSettingsCollection conStrings = ConfigurationManager.ConnectionStrings;

    Then the following exception occurs: System.Configuration.ConfigurationErrorsException: Configuration system failed to initialize —> System.ArgumentException: Illegal characters in path.

    This appears to stem from the .NET (2.0) Framework’s failure to determine if the application has a configuration file using a path containing the prefix ‘\?’.

    Please is there any way around this?

  11. mikey123 says:

    If a CGI .EXE program invokes a COM visible .NET DLL which executes, for example: ConnectionStringSettingsCollection conStrings = ConfigurationManager.ConnectionStrings;

    Then the following exception occurs: System.Configuration.ConfigurationErrorsException: Configuration system failed to initialize —> System.ArgumentException: Illegal characters in path.

    This appears to stem from the .NET (2.0) Framework’s failure to determine if the application has a configuration file using a path containing the prefix ‘\?’.

    Please is there any way around this?

  12. Daniele Levis says:

    In my application I have the same problem exposed…

    I have a CGI that invokes a COM .NET DLL which make a connection to a database.

    A soon as the COM make the Connection.Open it generates an exception: Configuration system failed to initialize —> System.ArgumentException: Illegal characters in path.

    I use IIS 6.0 as the Web Server.

    Is there any work around to solve this? It’s urgent i need absolutely help.

  13. I have solved the issue adding the CGI exe in the allowed ISAPI extension, under the properties of the Web Site.

  14. David.Wang says:

    mikey123, Daniele – hmm, weird resolution. I see no reason why Web Service Extensions in IIS has any interaction with Managed code parsing of a path string, but if it works for you, great!

    //David

  15. Daniel says:

    I have the same issue as Daniele but in IIS 7 after calling sqlconnection. Not good for interoperability.

  16. mikey123 says:

    In the .NET Framework code, ClientConfigPaths(), exePath is null and Assembly.GetEntryAssembly() is null. This leads to a call to UnsafeNativeMethods.GetModuleFilename() which obtains a path prefixed by \?. The path is then fed to Path.GetFullPath() which raises the “Illegal characters in path” exception.

    One workaround is to set an ISAPI extension for the specific .EXE in IIS. A path is then obtained without the prefix \?. But this approach gets unmanageable when multiple .EXE’s are involved and a less arcane solution is to be hoped for.

    IIS must find and execute the .EXE in order for it to invoke the DLL which implies the start up path is canonical and, that the Framework uses \? internally for its own reasons suggests it is not a complete faithless agnostic. But it raises the question as to why, in this context, the Framework uses unconditionally a path with \? when it is futile to do so.