Integrated Pipeline and the kernel-mode cache

I ran across an interesting issue while writing a sample .NET module for IIS7 the other day. The sample module was supposed to restrict requests to localhost only (you could of course do this by setting the site bindings to 127.0.0.1:80:localhost). Here is the code:

using System;
using System.Web; 

namespace
IIS7Demos
{
    public class LocalhostOnly:IHttpModule
    {
        #region IHttpModule Members
        void IHttpModule.Dispose()
        {
            return;
        }
         void IHttpModule.Init(HttpApplication context)
        {
            context.AuthorizeRequest +=
                (new EventHandler(this.AuthorizeRequest));
        }
         void AuthorizeRequest(Object source, EventArgs e)
        {
           
HttpRequest Request = ((HttpApplication)source).Context.Request;
           HttpResponse Response = ((HttpApplication)source).Context.Response;
           // allow access if address is localhost (127.0.0.1 if IPv4 and ::1 if IPv6).
           // Also allow access if client address matches server address
           if (Request.UserHostAddress == "127.0.0.1" ||
               Request.UserHostAddress == "::1" ||
               Request.ServerVariables["LOCAL_ADDR"] == Request.ServerVariables["REMOTE_ADDR"])
           {
               return;
           }
           else
           {
               Response.Status = "404 Access Denied. Localhost Requests only. ";
               Response.StatusCode = 404;
               Response.End();
               ((HttpApplication)source).CompleteRequest();
            }
        }
         #endregion
    }
}

The weird thing was that I was able to access some of the content from a remote machine under certain conditions. It took me a while to figure out why.

IIS7 puts the response for a particular request into the kernel mode cache if the kernel mode caching is not explicitely disabled during the lifetime of a request. A lot of components in the IIS pipeline disable kernel mode caching for particular requests, all authentication modules for example. Here is a list of all the reasons why content might not be cached: http://support.microsoft.com/kb/817445/en-us

The response gets put into the kernel-mode cache however if none of the configured IIS modules disabled the kernel mode cache for a particular request. That means that HTTP.SYS will send the response for a particular URL directly from kernel-mode when the next request comes in. IIS and the IIS modules won't even see the request. And exactly that happend to me.

You didn't see an issue like this in IIS6 because .NET modules were running inside the ASP.NET ISAPI extension which turned off kernel-mode caching. In the Integrated Pipeline that's not the case anymore.

THE FIX:
If you write a managed module that has to see every request (all modules that do authentication or authorization are good examples) you need to disable the kernel-mode cache. The DisableKernelCache function is a method on the Response object. One of the first lines of my module has to look like this:

Response.DisableKernelCache();

Oh, and by the way:
If you want to see what responses are in the kernel-mode cache try the command:

netsh http show cache

If that doesn't show any responses in the cache you can simply request http://localhost/welcome.png a couple of times via Ctrl+F5 in IE and run the command again.

 

No Comments