Wednesday, June 20, 2007

Beware The Evil Shadow

Evil Shadow Object-oriented programming principles have been an excellent addition to software development but one component that inevitably appears in OO languages has confounded me by it's uselessness. In VB.NET it is known as the "Shadows" modifier, in C# it is found in class member declarations sharing the name "new".

The purpose of this modifier keyword is to allow a subclass to redefine a property, field, or method of the base class where the base class has not been declared "Overridable" or "virtual" in VB.NET or C# respectively. Unfortunately it suffers from a major drawback, as demonstrated by the following code:

class Base
{
   virtual public string Extendable() { return "Extend"; }
   public string Locked() { return "Lock"; }
}

class Foo : Base
{
   override public string Extendable() { return "Food"; }
   new public string Locked() { return "Picked"; }
}

Base b = new Base();
b.Extendable(); // returns "Extend"
b.Locked(); // returns "Lock"

Foo f = new Foo();
f.Extendable(); // returns "Food"
f.Locked(); // returns "Picked"

Base bf = new Foo();
bf.Extendable(); // returns "Food"
bf.Locked(); // returns "Lock" !!!

As you can see, if the Foo methods are called via a Foo-typed variable, there is no difference between shadowing and overriding. However, as soon as you call a Foo method via a Base-typed variable, or pass a Foo instance to another class expecting a Base, you lose the functionality of Foo.

If "new" is omitted from Foo's implementation of Locked, Visual Studio reports an error: The keyword 'new' is required on 'Locked' because it hides method 'Base.Locked()'. It is very easy for someone to simply add the keyword as required with realising the consequences.

Occasionally, all you might want to do is utilise some existing behaviour in a base class and add some more features and shadowing or member hiding is fine. The fact that you have used shadowing is not obvious in the calling code though.

Most of the time, I want to alter the behaviour of a base class and pass my new subclass to existing code, perhaps in the Framework, that is only expecting the base type. Beware.

Waste Management

The script in my last post for removing old files from the temporary folder is only necessary because so many applications don't clean up after themselves. Hopefully, if the applications you are developing are doing the right thing they will be using in memory streams where ever possible. Only write to disk when absolutely necessary, creating random temporary files in the environment temp folder, not program folders with restricted access to non-admin users:

string myTempFile = System.IO.Path.GetTempFileName();

However, remembering to track and delete these files when you are finished requires more effort and is often ignored, creating the ever growing temp folder problem. There is a lesser known class in the framework that can help you though:

var myTempColl = new System.CodeDom.Compiler.TempFileCollection();
string myTempImageFile = myTempColl.AddExtension("png");

Excuse the C# 3.0 syntax in the last snippet, I hate the redundancy of repeating long type names, especially on the same line. When you have finished using all the temp files you created with the TempFileCollection, you can dispose it explicitly, automatically deleting the files, or if you're really lazy, let the GC take care of it.

myTempColl.Dispose();

PowerCleaning

I've often wanted to easily delete all old files below a folder that haven't been modified for a while. I've written a very small PowerShell function to do just that. By default it emulates the Disk Cleanup tool in Windows and deletes all files older than one week from the environment temp folder.

function Remove-TemporaryItems ([string] $path = ($env:TEMP), [TimeSpan] $age = (New-Object System.TimeSpan 7, 0, 0, 0) ) {
    $files = Get-ChildItem $env:TEMP -recurse | `
        Where-Object { ! $_.PSIsContainer -and $_.LastWriteTime.Add($age) -le [DateTime]::Now };
    $files | Remove-Item;
    $files;
}

 Tuesday, June 19, 2007

Malware And Least Privilege, Secure Enough?

Jeff Atwood had some trouble with malware last week. His digging led him to blame a particular website for infesting his system. In a comment on his post, I proposed, based on my experience, that simply running as a non-administrator user instead on his clean unpatched Windows XP SP2 installation should have protected him for the most part.

I'm not suggesting that patching is a low priority but from my (potentially outdated) understanding of malware attack vectors, running as a least privileged user would have gone a long way. I tried to find some good websites to verify my assumptions but didn't have much luck. What follows is my interpretation of the situation, please leave a comment if I have underestimated the capabilities of today's malware.

  • Ethernet connector virus figure Most malware wants to get installed on your PC and survive a reboot: A non-admin user only has privileges to write to "safe" areas of your file system and registry. Malware would be unable to replace commonly used system files. It would be unable to install itself as a driver or service with higher privilege. It would be unable to schedule itself to run when another user logs on. It would be unable to place shortcuts in an area that another user would see in their profile. Lease privilege users cannot install browser plugins. System level root-kits should be unable to hook in and a reboot with a Live CD should be able to see and remove anything evil.
  • Most malware wants to provide remote access to your PC: XP SP2 ships with the firewall enabled. Malware would be able to phone home but would be unable to open a port and await incoming connections. Modern broadband routers naturally prevent incoming connections too. The malware would still only be able to give the remote user least privilege access to the system.
  • Most malware wants you to give it something: As a non-admin malware would not be able to modify your drivers/etc/hosts file to redirect network traffic. It would be able to spawn many popup windows attempting to sell you something but this would be nothing more than annoying. It could scan your user documents for email addresses but hopefully your contact list is online (eg Gmail) or in Outlook (which warns the user when accessed programmatically). It could log key presses and grab sensitive passwords, which is definitely a concern, but this may be reduced by the "remember my password" features in software.

Jeff's situation was an extreme example of an unprotected system. Personally, I'm using Vista 64-bit which immediately reduces the amount of malware that is actually compatible. I'm also running non-admin with UAC on so anything questionable needs my permission. I have Internet Explorer 7 which displays the Information Bar whenever something wants special permission. Data Execution Prevention and ASLR are on by default to minimise exploitation of buggy system code. I still have Windows Defender and run Ad-Aware occasionally. Every installed application is patched to the latest update. Lastly, I avoid questionable material and have another separate non-admin user account if I really need to try something.

I'm sure I'm not 100% safe, but I don't think that's currently attainable. Any machines I remove malware from for friends get returned with a non-admin account and they only come back to me with more problems after sheepishly admitting they went back to an admin account. I have read that the new hardware-virtualization features in new CPUs (which I have enabled) could allow malicious code, and then there is everything else I haven't heard of.

I think I've rambled enough and probably missed many things but I'm interested in reading your thoughts on the matter. If you can recommend a good source for the latest PC security information I'd appreciate that too.

 Monday, June 18, 2007

ClickOnce and Terminal Services

Under just the right conditions that I have been lucky enough to meet, .NET applications deployed by ClickOnce will not start under a Terminal Services session. At the heart of the problem is this error message:

Shortcut activation from http/https or UNC sources is not allowed.

I could not find anything in Google, on the MSDN forums or in the Microsoft Support Knowledge Base. With some trial and error and a little reflection I've tracked it down to this very special combination:

  • ClickOnce offline install mode is enabled
  • and the application is started via the Start Menu shortcut
  • and the user is logged into a Terminal Services session
  • and Terminal Services is redirecting the Start Menu to a UNC.

Changing any one of these conditions is enough to avoid the problem and would probably explain why it is documented anywhere. The error message is triggered by code in the System.Deployment assembly and it it cannot be overridden by a system setting either.

Redirecting the Terminal Services Start Menu to a UNC is also a common practice when running multiple terminal servers, or a shared Start Menu for all users (as was my case).

I will be recommending users simply start the apps via the same ClickOnce deployment URL that was used for the initial install and try to disable TS folder redirection where possible.

It seems ClickOnce uses the shortcut location later in the bootstrap process and the problem exists in Orcas too so I don't think Microsoft will be fixing this one.

 Friday, June 15, 2007

Rounding

In the .NET Framework, the System.Math class has methods for Ceiling, Floor, Round, and Truncate. Round has an overload for specifying the number of decimal places to round to but it doesn't support other intervals, for example: rounding to the nearest five cents.

The mathematics involved for supporting other intervals is quite simple when you know it but getting there and verifying it can take a few moments. Here is the general solution to save time next time I need it, or if someone else is looking for the same thing:

decimal offset = 0.05M;        // interval we are rounding to (5 cents)
decimal value = 1.23M;         // value to be rounded
decimal temp = value / offset;
temp = MyRoundToInteger(temp); // toward zero or banker's rounding function
decimal result = temp * offset;

You can exchange MyRoundToInteger for any of the afore mentioned Math class methods or even one of your own custom rounding techniques, as long as it rounds to zero decimal places

 Thursday, June 14, 2007

Premature Analysation

I was thinking today as I was resolving a few Code Analysis violations that perhaps I should stop. I will assume you are familiar with the quote "Premature optimisation is the root of all evil" and if you're not, you should be.

I've often had the feeling that something wasn't quite right when I adjusted my code to satisfy the various CA performance warnings. Today I have investigated that feeling further:

Winner of Zurich Marathon 2007 CA1809: Avoid excessive locals - Triggers when you have more than 64 local variables in a method because the processor registers can no longer hold them all and some will be moved to and from the stack. If you have this many variables in a single method you have other problems to worry about.

CA1811: Avoid uncalled private code - Triggers when a private method exists that isn't called directly. This is great for finding and removing dead code but this is more about readability and not runtime performance.

CA1812: Avoid uninstantiated internal classes - Very similar to CA1811 but at the class level. Once again, good for removing dead code but not really performance related.

CA1807: Avoid unnecessary string creation - This is for people who still insist of using ToUpper or ToLower on strings before performing comparisons. Embrace the Unicode world and pass the StringComparison enumeration to String.Equals and String.Compare calls. This rule belongs in the Globalization set.

CA1813: Avoid unsealed attributes - This might be the first real performance rule so far but it does apply specifically to reflection on custom attributes. If your application makes heavy use of attributes, you may want to pay attention here. I think though that your personal philosophy on preferring extendable classes versus sealed classes may have more weight here.

CA1801: Review unused parameters - Once again, this is an excellent rule for locating dead code that may be misleading or unnecessarily complicated to maintain. I am beginning to wonder if the Microsoft.Performance CA category is a misnomer.

This covers the first third of the 18 rules on the performance warnings list. So far only one could potentially undermine the performance of your application given the right conditions and I'd run an actual test to time this before I started making big changes.

However, the rules shouldn't be ignored, perhaps they should just be considered from a point of view other than performance. Hopefully investigating the rest of the list, which I plan to do in future posts, will reveal valid rules for improving performance. I'm expecting at least CA1818 to come though for me there.

Strength In Numbers

Since Visual Studio 2005 has made the whole process much easier, I strong name every assembly I work on. It's one of the first things I do when I add a new project to a solution: Choose a name, set strict compilation warnings, enable Code Analysis, assign a strong name key.

I have used a few open source projects lately and have been disappointed that the binaries haven't always been strong name signed. This means I have to get the source instead, setup a new key and adjust their build script to use it before I can even add it as a reference to my own project.

Weight Lifting There are several benefits to using strong names:

  • Optional deployment to the GAC.
  • Satisfy Code Analysis rule CA2210.
  • Use of the InternalsVisibleTo attribute.
  • Reference from other strong named assemblies.

I don't think I've ever deployed any assemblies to the GAC, so only the last three are relevant for me but I'm sure GAC deployment is important to people writing COM interop code or add-ins for unmanaged applications or it will be important one day when their project gets reused in that context.

The counter-argument is that strong names are supposed to provide security and handing out the private key file with rest of the source would defeat the purpose. However, Mike Downen, program manager for security on the CLR team, says this:

They have no revocation capabilities and they don't expire. Nor do strong names inherently provide any way to securely map a public key to a particular publisher. Thus, strong names should be used very cautiously when making security decisions.

For these reasons I think it is a good idea to strong name all assemblies. Perhaps use a different strong name private key for each program. The owner(s) of an open source project could even retain a private key separate from the one distributed by the open source repository and use it for signing official binary releases.

 Tuesday, June 12, 2007

Code Camp Is In Tents

A tent on the beach I was privileged to attend Code Camp Oz 2007 in Wagga Wagga this year and I highly recommend it to anyone working with .NET. Unfortunately not everyone can find the time to drive twelve hours across the country for a weekend.

However, anyone living in South Australia will have an opportunity to attend the upcoming Code Camp SA, a free event sponsored by the University of South Australia, the Adelaide .NET Users Group, and the Australian Computer Society.

It will be held at the UniSA City East West campus for both days of the July 7th and 8th weekend. Specific details and registration can be found on the ACS web site for Saturday and Sunday. When available, the scheduled program will be published on the ADNUG news page.

If the content and quality of Code Camp Oz is anything to go by, this is an event that shouldn't be missed. See you there.

 Monday, June 11, 2007

Home Hardware

I write this as the Queen's Birthday long-weekend in Adelaide comes to a close. My fiancee has been busy redesigning the front yard and I have been busy with all things technical as I usually do.

On all three days of this weekend - Saturday, Sunday, and the public holiday Monday - my fiancee has been to the local Bunnings Warehouse home improvement store to purchase tools, plants, and pavers for the garden. They were open 9am to 5pm all three days.

Closed Hardware Store Today, the public holiday Monday, I discovered that it would be really helpful if I had a wireless USB adapter to ease the repair of a friend's PC from the comfort of my home office. Unfortunately, I live in South Australia, where we have legislation known as the Shop Trading Hours Act.

This Act, allows home improvement and furniture stores, among others, to be open on public holidays but it denies this same right to electronics and computer stores. This also has the absurd side-effect that Harvey Norman stores open for the sale of furniture and bedding but close access to the computers and communications section of the building.

Why is the consumer allowed to build a garden shed or install a new light fitting on a holy day or a public holiday but not permitted to install a new hard drive or fit surround sound speakers in their lounge? A March 2007 review of the Shop Trading Hours was heavily in favour of maintaining these ridiculous constraints despite the increasingly 24x7 nature of today's lifestyle.

Each business should have the option to open for trading if and when they choose. This decision should be based on the demand for their goods and services on certain days and times, the affordability of staff wages at weekend and holiday penalty rates, the personal beliefs of the owners, and anything else that may impact the profitability of the type of business in question.

Likewise, the devout consumer will prefer to spend Sundays and holidays at their place of worship or having quality time with their family while those without religious concerns or family commitments and wanting to get the most of their busy schedule can shop for items they need.

 Saturday, June 09, 2007

Three Strikes

I bought a LinkSys WAG54Gv2 ADSL wireless modem a couple of years ago. It had a good price and listed many features but I've suffered with unreliable WiFi since and eventually added a standalone access point to my home network.

The local broadband community has a lot of complaints about the LinkSys modem too and the original v1 wasn't any better. For a company advertised as "A Division of Cisco", I was hoping for much more and I wasn't the only one.

As a result I usually steer friends toward something like the D-Link DSL-G604T, a modem that I've installed many times with reliable results. Recently though, a close friend, finally moving from dial-up to broadband, purchased a LinkSys WAG54Gv3 against my advice. I guess the price, the Cisco brand association and the feature list were more convincing than my protests.

Last night I helped install the new modem and configure a Notebook, a Wii, and an iPaq (sounds like the beginning of a joke) to connect to it wirelessly. A fourth player, a wired HP desktop PC, was used to initially configure the modem and couldn't have been easier. WiFi however, as it turned out, actually was a joke.

On all three devices, IP addresses were not being issued by the modem if WEP encryption was enabled but WAP and also no-encryption were fine. The fact that I tried WEP first is entirely another blog post but can be summarised as such: better than no encryption, combined with MAC filter, LinkSys overheat with WAP, and minimal possible damage from an attack.

DHCP over WiFi has been a recurring issue in my experience and it is very disappointing. It is incredibly fundamental and there seems to be an amazing lack of thought put into getting this functionality right. The WAG54Gv3 is new to the market and doesn't have too much feedback yet, but so far it looks like it could be LinkSys' third strike.

I've said before that buying the more expensive product in the first place would probably solve all this messing around, but where should the line be drawn? An entry level Cisco 857 ADSL modem router will easily set you back AU$700, that's double the price of almost every other option.

DasBlog Mobile

Today I had the pleasure of visiting this web site with a Windows Mobile device. I was thoroughly pleased to discover that, without any configuration on my part, the blog rendered in Pocket Internet Explorer in a very clean, usable form.

I was even able to read the comments and submit my own. I must say though that the limited input capabilities of many PDAs really emphasise how annoying CAPTCHA can be.

Anti-spam mechanisms aside, the DasBlog developers have done an excellent job to provide yet another feature out-of-the-box that I was worried I would have to implement myself.

Thank you.

 Friday, June 08, 2007

Small Business, Big Problems

I've had the pleasure of chasing down some complications on a recent Small Business Server 2003 R1 deployment for a client. As is our preferred style for building new machines, we installed Windows then we ran Microsoft Update over and over, rebooting in between as necessary until every available critical and optional update had been installed.

We then proceeded to deploy our software's prerequisites to the server followed by the software itself, and lastly configure the server ready for deployment to the client's network. This last part was interesting... the SBS licensing GUI would crash when we attempted to start it. Some extensive Googling found this KB article suggesting to disable DEP for the offending program. Problem solved.

A little later the program for creating Active Directory Users was crashing exactly the same way. I couldn't find any KB articles for this program but I tried disabling DEP for the entire system and rebooted. Problem solved again. However, I wasn't happy that turning off DEP was right.

I spent an evening wandering through forums and blogs about SBS and eventually concluded that SBS SP1 was supposed to solve the problems. Of course, we had installed all the Microsoft Updates so that should already be fixed right? Sadly, no.

It turns out that because Service Pack 1 for SBS2003 apparently changes too much it doesn't get offered via Windows Update, you have to find it, download it, and install it manually. To make things worse, SP1 for SBS is supposed to be installed before SP2 for Windows Server 2003 but Microsoft Update had already installed that.

Several hours later we had uninstalled Windows Server SP2, installed the multi-file SBS SP1 update, reinstall Windows Server SP2, broken the .NET 2.0 framework, repaired that, broken SQL 2005 Reporting Services then reinstalled that, and finally reapplied SQL 2005 SP2.

Next time I think we'll have to remember to install SBS 2003 R2.