Sunday, September 5, 2010

When WCF blows the whistle (Part1)

By default WCF will blow the whistle on applications when they exchange data (by calling a service) that exceeds certain limits imposed by the WCF runtime. These are limits regarding the size of messages, the complexity of the message (such as the number of objects in the object graph), the size of elements in the message and other characteristics of the data exchanged. Exceeding this limits result in the breaking the communication and reporting of this situation as an exception, either on the client or the service side.

WCF is a defensive system, meaning that the limits imposed on the exchanged messaged are deliberately chosen ‘ as small as reasonably’ possible. This is (imho) a healthy approach, especially in the context of public exposed services.

This means that if you run against these limits, the following questions should arise first:

  1. Is this really what I intend to achieve?
  2. Can/should I change the design in order to fit into the default limits?
  3. What are the risks if I decide to relax the limitations?

In this series of posts I will discuss the various WCF limits and the ways to change the default settings.

 

Limit 1. ReaderQuotas

Data contracts are a potential entry point for hackers. One of the types of attacks are ‘Denial of Service’ attacks in which a user sends a vast amount of data to the service so that the service spends most of its time simply trying to receive and read the data and performance suffers accordingly. In order to avoid this type of attacks it is advisable to define messages that cannot contain nested data structures, arrays or collections of indeterminate length. The limits on the message structure are imposed by ‘ReaderQuotas’. 

‘ReaderQuotas’ are defined per binding, as such they can be different for each service endpoint. The ‘ReaderQuotas’ are checked by the receiving side (being either the server or the client).

Following quota can be defined:

  • MaxArrayLength: maximum number of bytes in an array. (default=16384)
  • MaxStringContentLength: maximum length of a string type member in the message. (default=8192)
  • MaxDepth: maximum level of nesting structures in the message. (default=32)
  • MaxBytesPerRead: maximum number of bytes per read. This quota limits the number of bytes that can be consumed by the reader during a single call to the Read() method. This quota is an approximation, because transformations in the encoding layer happen before this quota is applied. This quota is closely tied to the number of bytes received on the wire at the transport level, but its purpose is to control the quantity of data we receive for each read. In practice, it is used to limit the size of start tags. Because the entire start tag must be buffered to be processed (attributes uniqueness must be verified), the size must be limited to mitigate DOS attacks.(default=4096)
  • MaxNameTableCharCount: maximum number of characters allowed in a table name. This quota limits the total number of characters in strings that are atomized in the NameTable for the reader. When strings are atomized they are inserted into a NameTable and never removed. This can cause the buildup of large amounts of character data in a NameTable. This quota places a limit on how much data can be buffered in the reader's NameTable. (default=16384)

Internally ‘ReaderQuotas’ apply to the XmlDictionaryReader type used internally to manipulate the soap message.

Configuring ReaderQuotas

In configuration file:

   1: <system.serviceModel>
   2:   <bindings>
   3:     <basicHttpBinding>
   4:       <binding>
   5:         <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
   6:               maxBytesPerRead="4096" maxNameTableCharCount="16384" />
   7:       </binding>
   8:     </basicHttpBinding>
   9:   </bindings>

In code:

Setting the ReaderQuotas in code is not that straightforward because the ReaderQuotas property is not defined on the Binding base class. A little reflection can help as shown in the next example:

   1: using (ServiceHost host = new ServiceHost(typeof(TestService), new Uri("http://localhost:80/TestService"), new Uri("net.tcp://localhost:100/TestService")))
   2: {
   3:     host.AddDefaultEndpoints();
   4:     foreach (ServiceEndpoint endpoint in host.Description.Endpoints)
   5:     {
   6:         PropertyInfo propInfo = endpoint.Binding.GetType().GetProperty("ReaderQuotas");
   7:         if (propInfo != null)
   8:         {
   9:             XmlDictionaryReaderQuotas qoutas = propInfo.GetGetMethod().Invoke(endpoint.Binding, null) as XmlDictionaryReaderQuotas;
  10:             qoutas.MaxBytesPerRead = 10000; // set individual value
  11:         }
  12:     }
  13:     host.Open();

Another option is to derive your own binding type form the WCF provided bindings and tune the ‘ReaderQuotas’ to the desired value.

   1: //-----------------------------------------------------------------------
   2: // <copyright file="MaximizedNetTcpBinding.cs" company="RealDolmen N.V.">
   3: //     Copyright (c) RealDolmen. All rights reserved.
   4: // </copyright>
   5: // <author>Luc Van Keer</author>
   6: //-----------------------------------------------------------------------
   7: namespace Bromo.TaskChannel
   8: {
   9:     using System;
  10:     using System.Collections.Generic;
  11:     using System.Linq;
  12:     using System.Text;
  13:     using System.ServiceModel;
  14:     using System.Xml;
  15:  
  16:     public class MaximizedNetTcpBinding : NetTcpBinding
  17:     {
  18:         #region Constructors
  19:  
  20:         public MaximizedNetTcpBinding()
  21:             : base()
  22:         {
  23:             this.MaximizeSettings();
  24:         }
  25:  
  26:         public MaximizedNetTcpBinding(SecurityMode securityMode)
  27:             : base(securityMode)
  28:         {
  29:             this.MaximizeSettings();
  30:         }
  31:  
  32:         public MaximizedNetTcpBinding(string configurationName)
  33:             : base(configurationName)
  34:         {
  35:             this.MaximizeSettings();
  36:         }
  37:  
  38:         public MaximizedNetTcpBinding(SecurityMode securityMode, bool reliableSessionEnabled)
  39:             : base(securityMode, reliableSessionEnabled)
  40:         {
  41:             this.MaximizeSettings();
  42:         }
  43:  
  44:  
  45:         #endregion
  46:  
  47:         #region Properties
  48:         #endregion
  49:  
  50:         #region Private Methods
  51:  
  52:         /// <summary>
  53:         /// Maximizes the settings.
  54:         /// </summary>
  55:         private void MaximizeSettings()
  56:         {
  57:             this.ReaderQuotas = XmlDictionaryReaderQuotas.Max;
  58:         }
  59:  
  60:         #endregion
  61:     }
  62: }
 
Conclusion

The ‘ReaderQuotas’ default values are reasonable values for most services. If you decide to change the defaults, there must be a good reason to do so and the first approach should be to question the design and try to make it operate with the default values.

Following test illustrates the danger of augmenting for example the ‘maxDepth’ value (which at first view might look like a harmless change). This test sends the ‘Employee’ message to the service. The Employee message contains a ‘Manager’ reference to another employee instance. Following graph shows the processor load when performing 500 operation call with a delay of 10msec between each call, sending either a normal 2 level ‘Employee’ structure and a abnormal 1000 level structure.

mdepth

1 comment:

Karan Vohra said...

Awesome Post!! Keep up the good work!!