After debugging some connection problems I found that the wscale normalization concept is flawed. I’ll describe here what is wrong with it and then move on to suggest a different solution I’m currently testing. The problem I was seeing is that some connections to some webservers stalled without an apparent reason.
First a quick reminder of why I originally came up with the wscale normalization. Stream4 originally doesn’t look at the window scaling value when determining the TCP window. This causes it to be wrong about the TCP window in about every connection, which is one of the reasons out of window packets are not dropped (this is actually a gaping evasion hole since these packets are not used in stream reassembly). This is why I decided to add window scaling support to the stream4inline extension. This works great and allows the admin to drop out of window packets. There is a problem associated with it though. The maximal window that is possible with wscaling is 1GB. This would mean that Snort_inline would in the worst case have to queue almost 1GB of data in it’s buffers for a single stream. To prevent this being used by an attacker to attack Snort_inline, I wanted give the admin the option to set a maximal wscale size.
So, why doesn’t replacing the wscale value in packets work? I’ll explain that now. First an example without normalization. Say we have client connecting to a server. The client sends in it’s SYN packet a window of 5840 and a wscale of 5. The server replies with a SYN/ACK with window 5792, wscale 9. Both have a unscaled window in their packet since the wscale won’t be used before both sides have received a packet with the wscale option enabled. The client sends an ACK completing the three way handshake, with a window of 183. That means a scaled window of 5856 (183 x 2^5). The client will now send an actual data packet, using the same window. The server ACK’s the data with a packet with a window of 16, meaning a scaled window of 8192 (16 x 2^9).
Now, what happens when we normalize? Consider the same connection, but now Snort_inline normalizes all wscale values above 2, to 2. The client sends in it’s SYN packet: window of 5840, wscale of 5, but due to the normalization, the server receives it as window of 5840, wscale of 2. The server replies with a SYN/ACK with window 5792, wscale 9, but the client receives it as window 5792, wscale 2. The problem here is that neither the client or the server know that it’s wscale value was been modified. Nor is there a way to make it known. So what then happens is this. When the server wants to say it has a window of 8192, it will send a packet with the window field set to 16 (16 * 2^9 = 8192). But, due to the normalization, it actually says it has a window of 64 (16 * 2^2). Likewise, when the client wants to tell the server it has a window of 5856 (window field set to 183), it actually says it has a window of 732 (183 * 2^2). This completely stalls connections. So why did I only see this on some rare connections? That is because most servers on the internet use low wscale values. The server I ran into issues with however, used a value of 9.
The solution I am now testing is normalizing the scaled window. With this idea Snort_inline takes the full scaled window into account and compares it with a maximum value. If it exceeds it, the window value in the packet is modified taking the wscale value into account. I’ve been running like this for about 2 weeks now, and so far I have seen no stalling connections anymore. There is however quite a drawback to this approach. The window size is a constantly changing value that is adapted in almost every TCP packet. Unlike the wscale normalization, that could be done by modifying the SYN and SYN/ACK packets, the new approach in the worst case has to modify and replace almost every single packet in a stream. This will take more resources from Snort_inline.
I’m interested in hearing other possible solutions to this problem or other drawbacks of my new solution. I will be checking my new solution into SVN soon. I will make sure it is disabled by default. To work around the broken wscale normalization just set it to it’s maximum value, so add ‘norm_wscale_max 14’ to your stream4 configuration line.