Writing a Managed Internet Explorer Extension: Part 3
I’m debating where to take this little series, and I think I am at a point where we need to start explaining Internet Explorer, and why writing these things can be a bit tricky. I don’t want to write a blog series where people are blindly copying and pasting code and not knowing what IE is doing.
I am not a professional at it, but I’ve written browser extensions for most popular browsers. IE, Chrome, Firefox, and Safari. In terms of difficulty, IE takes it. That’s probably why there isn’t a big extension community for IE. Let’s go in the Way Back Machine…
IE at it’s pinnacle, IE was 95% by web surfers with IE 5 and IE 6. If you are a developer, you probably hear a lot of criticisms for IE 6, and rightly so. Back then, IE supported a plug in model with that notorious name ActiveX. It was criticized for allowing web pages to just ship run arbitrary code. Of course, all of that changed and now IE really gets in your face before one of those things run. In fact, it is one of the reasons why intranet apps still require IE 6. Regardless, the message was clear to Microsoft. We need security!
Security was addressed in IE 7, and even more so in IE 8 with the help of Windows Vista and Windows 7.
Hopefully by now you’ve had the opportunity to play around with writing IE Add Ons, but you may have noticed some odd behavior, such as accessing the file system.
UAC / Integrity Access
UAC (User Access Control) was introduced in Windows Vista. There was a lot of noise over it, but it does make things more secure, even if that lousy dialog is turned off. It’s just transparent to the user. The purpose of UAC is the Principle of Least Privilege. Don’t give a program access to a securable object, like a file, unless it needs access to it. Even if your application will never touch a specific file, another application might figure out a way to exploit your application into doing dirty deeds for it. UAC provides a mechanism for temporarily giving access to securable object the application would normally not have permission to. UAC introduced the concept of Elevated and Normal. Normal is what the user normally operates under until a UAC prompt shows up.
Those two names are just used on the surface though… there are actually three Integrity Access Levels. Aptly named, they are called Low, Medium, and High. Medium is Normal, and High is Elevated.
IE is a program that use Low by default. Low works just like threads and process tokens. In theory, you could run your own application in “Low”. Low is it’s own SID: “S-1-16-4096”. If we start a process using this SID, then it will be low integrity. You can see this article for a chunk of code that does that. It’s hard to do this in managed code, and will require a good amount of platform invoke. You can also use this technique with threads.
Ultimately, Low mode has some really hard-core security limitations. You have no access to the File System, except a few useful places
- %USERPROFILE%\Local Settings\Temporary Internet Files\Low
- %USERPROFILE%\Local Settings\Temp\Low
That’s it. No user documents, nada. Some of those directories may not even exist if a Low process hasn’t attempted to create them yet. If your extension is going to only be storing settings, I recommend putting them into %USERPROFILE%\AppData\LocalLow. This directory only exists in Windows Vista and up. Windows XP has no UAC, and also it has no protected mode, so you are free to do as you please on Windows XP!
To determine that path of LocalLow, I use this code. A domain policy might move it elsewhere, or it might change in a future version of Windows:
1: public static class LocalLowDirectoryProvider
3: private static readonly Lazy<string> _lazyLocalLowDirectory = new Lazy<string>(LazyGetLocalLowDirectory, LazyThreadSafetyMode.ExecutionAndPublication);
5: public static string LocalLowDirectory
9: return _lazyLocalLowDirectory.Value;
13: private static string LazyGetLocalLowDirectory()
15: var shell32Handle = LoadLibrary("shell32.dll");
18: var procAddress = GetProcAddress(shell32Handle, "SHGetKnownFolderPath");
19: if (procAddress == IntPtr.Zero)
21: return null;
28: var localLowSavePath = IntPtr.Zero;
31: if (SHGetKnownFolderPath(new Guid("A520A1A4-1780-4FF6-BD18-167343C5AF16"), 0, IntPtr.Zero, out localLowSavePath) != CONSTS.S_OK)
33: return null;
35: return Marshal.PtrToStringUni(localLowSavePath);
39: if (localLowSavePath != IntPtr.Zero)
46: [DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "SHGetKnownFolderPath")]
47: private static extern uint SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr pszPath);
49: [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "GetProcAddress", CharSet = CharSet.Ansi)]
50: private static extern IntPtr GetProcAddress([In] IntPtr hModule, [In, MarshalAs(UnmanagedType.LPStr)] string lpProcName);
52: [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "LoadLibrary", CharSet = CharSet.Auto)]
53: private static extern IntPtr LoadLibrary([In, MarshalAs(UnmanagedType.LPTStr)] string lpFileName);
55: [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "FreeLibrary")]
56: private static extern IntPtr FreeLibrary([In] IntPtr hModule);
It returns null if there is no LocalLow directory. The Lazy<T> class provides some cool thread-safe caching for this value as it will never change (at least it shouldn’t).
However, if you need to access the file system outside of one of these white listed directories, you have a couple of options later on down our journey:
- Use IE’s built in Open File and Save File dialogs. They will give you access to the file.
- Use a broker process / COM server. We’ll discuss this one later.
This tends to trick managed developers. Starting with IE 8, each tab is it’s own process. That tends to break what developers get comfortable with, like the fact that a static / shared variable are unique per tab. That was one of the design goals of decoupling tabs – they can only talk to each other through securable means, like RPC. Even in IE 7 which does not have a process per-tab, it still isolates the BHO instances from one another. As far as the BHO knows, a tab is a window.
Every time a new tab is opened, that tab gets it’s own instance of the BHO. This was originally done to keep IE 7 as backward compatible with BHO’s as possible. In IE 6, each Window was it’s own process. BHO’s got comfortable assuming there would only be one instance of itself running. This loose coupling will also change the behavior of how dialogs might be shown from a BHO. We’ll get into that when we discuss UI design and interaction.
Part 4, we will back back up making a BHO do useful things. I just felt I had to get this off my chest.