Search code examples
c#memorytcpclienttcplistener

StreamReader ReadLine Memory Leak


I am making a server and I've noticed it has a bit of an odd memory leak. On my server, I have limited the total connections that one IP address can make to 8. This works perfectly.

However when I use LOIC to DoS my server, I notice that whilst it only creates8 instances of the network class, The ReadLine method of the StreamReader takes up a large portion of memory.

An image of the VS Analyse

I have verified using Console.WriteLine that no data is actually being received, it simply waits at that method. And the memory increases and fills up my entire 16GB.

Just to note, there are only ever 8 instances of this class from one IP and as I'm DoS'ing and not DDoS'ing it only ever comes from my IP(I have verified this).

What is the cause of this problem and how can I fix it ?

EDIT:
The relevant code is:

        string data;
        Dictionary<string, string> variables = null;
        try {

            while (_listen) {

                data = null;

                while ((data = _reader.ReadLine()) != null) {

Solution

  • The ReadLine function will buffer the data into a string util it encounters a \r\n (\n alone won't do it). It uses a StringBuilder for that. So, if the data you receive doesn't contain the sequence \r\n, it'll end up allocating lots of memory for a single string.

    You have one simple solution here: you have to limit the maximum string length your server is willing to read. This is required for dealing with malicious data - never trust the data you receive if your server may be the victim of DoS attacks.

    So, the solution, as a TextReader extension method:

    public static String ReadLineSafe(this TextReader reader, int maxLength)
    { 
        var sb = new StringBuilder();
        while (true) {
            int ch = reader.Read();
            if (ch == -1) break; 
            if (ch == '\r' || ch == '\n')
            { 
                if (ch == '\r' && reader.Peek() == '\n') reader.Read(); 
                return sb.ToString();
            } 
            sb.Append((char)ch);
    
            // Safety net here
            if (sb.Length > maxLength)
                throw new InvalidOperationException("Line is too long");
        }
        if (sb.Length > 0) return sb.ToString();
        return null; 
    }
    

    This is the original ReadLine code, with the added security check.

    Oh, and you should wrap your NetworkStream in a BufferedStream for netowrk reads, in order to avoid calling recv on each byte.