.NET Framework: Corrupted State Exceptions

Introduction

Imagine the perfect user for your application. He/she never does anything unexpected, never clicks any wrong buttons and always chooses the right steps. That would be an awesome world to live in. But we all know that people make mistakes, click the wrong button, use software in the manner it wasn't intended to, etc. All these fault conditions lead to an undesired state of the application. It is precisely such anomaly conditions in application behavior that leads to programmers using exceptions to make their applications resilient. However in their quest to make their software more resilient, they also end up trapping exceptions which ideally should not be handled by their code.

Consider the following example:

  try
  {
  string fileName = FunctionToGetFileName();
  FileStream fs = File.Create(fileName);
  }
  catch(Exception ex)
  {
  Console.WriteLine("File could not be created");
  }

Here an exception can be raised for either of the following reasons:

  1. FunctionToGetFileName might throw an exception
  2. File.Create fails because of invalid file name or lack of disk space
  3. While executing the second argument, we run out of stack space to store the local "fs".

In essence, throwing an exception in the above code should not be interpreted as failure to create the file. However we see this pattern in a lot of applications today. Application developers in their quest to make their applications fool-proof fall prey to the trap of not checking for specific exceptions.

This behavior also resulted in use of "catch(Exception ex)" to trap exceptions which were not in user code but in application hygiene (like state of stack, access violations). This resulted in applications running while being in the possible corrupt state where an outright failure would be more preferable to prevent loss of data integrity.

Types of Exceptions

Managed applications can have two types of exceptions. One is the user mode exceptions, which arise of faulty logic in application code and are raised by the application. The other kind of exceptions is SEH exceptions (Structured Exception Handling) which refer to context outside of the application. Examples would include access violation and stack overflow. SEH exceptions indicate that the process integrity is compromised and the application can no longer be considered to be in a stable state.

Corrupted State Exceptions

In .NET Framework 4.0, the CLR is now marking those exceptions which can corrupt the process state as corrupted state exceptions and are treated differently from other exceptions. By default, such exceptions are no longer delivered to the CLR. This implies that these exceptions can no longer be trapped in a catch block by user code. This is done to ensure process and data integrity. If the state of process is compromised, you don't want to continue running lest your data gets corrupted.

How it worked in earlier versions of the .NET Frramework - To see the change in the behavior, let us consider the behavior of an SEH exception - access violation in pinvoked code.

Consider that we have native dll with the following code:

  #include <Windows.h>
  #include <stdio.h>
  #include <tchar.h>
  #include <strsafe.h>
  #include <cstdio>
  #define BUF_SIZE 255
  #define MAX_THREADS 8
  typedef struct MyData {
  int val1;
  int val2;
  } MYDATA, *PMYDATA;
  
  BOOL APIENTRY DllMain( HMODULE hModule,
  DWORD ul_reason_for_call,
  LPVOID lpReserved
  )
  {
  switch(ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH: 
  break;
  case DLL_THREAD_ATTACH:
  break;
  case DLL_THREAD_DETACH: 
  break;
  case DLL_PROCESS_DETACH:
  break;
  }
  return TRUE;
  }
  extern public "C" __declspec(dllexport) int PinvokeAV()
  {
  int *p ;
  p = 0;
  *p = 12;
  return 0;
  }
Listing 1 - pinvokeav.cpp



To compile the above code, open Microsoft Visual Studio Command Prompt and type the code above into a file called pinvokeav.cpp and type the following:

  cl /ld pinvokeav.cpp

This will create a native DLL called pinvokeav.dll Now create a v2.0 managed application which refers to this DLL.

  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.IO;
  using System.Runtime.InteropServices;
  namespace CorruptedStateExceptionsDemo
  {
  class OldBehavior
  {
  [DllImport("PinvokeAV.dll")]
  public static extern void PinvokeAV();
  static void Main(string[] args)
  {
  try
  {
  PinvokeAV();
  }
  catch(Exception ex)
  {
  Console.WriteLine("Caught an exception of type" + ex.GetType().Name);
  }
  }
  }
  }
Listing 2 - OldBehavior.cs

To compile this, execute the following command line (double check to ensure that you compile this against v2.0 CLR and not V4.0

  c:\windows\microsoft.net\framework\v3.5\csc.exe OldBehavior.cs

Now make a clone of OldBehavior.cs and call it NewBehavior.cs. To differentiate it from OldBehavior.cs, rename the class in NewBehavior.cs to NewBehavior.

  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.IO;
  using System.Runtime.InteropServices;
  namespace CorruptedStateExceptionsDemo
  {
  class NewBehavior
  {
  [DllImport("PinvokeAV.dll")]
  public static extern void PinvokeAV();
  static void Main(string[] args)
  {
  try
  {
  PinvokeAV();
  }
  catch(Exception ex)
  {
  Console.WriteLine("Caught an exception of type" + ex.GetType().Name);
  }
  }
  }
  }


Listing 3 - NewBehavior.cs

Compile NewBehavior.cs against v4.0 CLR using the command line below.

  c:\windows\microsoft.net\framework\v4.0.30319\csc.exe newBehavior.cs

Observing the change in behavior between pre- V4.0 and v4.0 managed application, now put Pinvokeav.dll in the same folder as OldBehavior.exe and NewBehavior.exe.

  Execute OldBehavior.exe

You will see that the AV generated inside of PinvokeAV.dll is catch in the user code and the following line is printed.

  "Caught an exception of typeAccessViolationException"

However when you execute NewBehavior.exe, the thrown inside of pinvokeav.dll does not get handled by the catch block and the application terminates with the following output.

   Unhandled Exception: System.AccessViolationException: Attempted to read or write
    protected memory. This is often an indication that other memory is corrupt.
      at CorruptedStateExceptionsDemo.NewBehavior.PinvokeAV()
      at CorruptedStateExceptionsDemo.NewBehavior.Main(String[] args)
 

What changed? The SEH exception in v4.0 is a Corrupted State exception and hence the user mode code is no longer allowed to handle this exception inside a catch block and hence the application is terminated.

Maintaining Legacy Behavior via HandleProcessCorruptedStateExceptionsAttribute

If you have a dependency on the legacy behavior, there is an attribute you must use to tag your code to ensure that you get the legacy behavior and your code can continue to trap SEH exceptions when compiled for v4.0. It is a very strong recommendation that you do not do that since it is against good programming practices.

In lieu of the attribute, you can also add an element <legacycorruptedstateexceptionspolicy> in the application configuration file to preserve the legacy behavior.

Summary

CLR's handling of SEH exceptions which compromise process state have changed in .NET 4.0 and you can no longer trap these exceptions in your code. Hopefully this article has provided enough information to code your application defensively.

Related Articles



About the Author

Vipul Vipul Patel

Vipul Patel is a Software Engineer currently working at Microsoft Corporation, working in the Office Communications Group and has worked in the .NET team earlier in the Base Class libraries and the Debugging and Profiling team. He can be reached at vipul_d_patel@hotmail.com

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Wednesday, September 24, 2014 8:00 AM - 9:00 AM PDT According to a recent Forrester Research report, many companies are choosing low-code platforms over traditional programming platforms, due to the speed with which low-code apps can be assembled and tested. With customer-facing applications on the rise, traditional programming platforms simply can't keep up with the "short schedules and rapid change cycles" required to develop these applications. Check out this upcoming webinar and join Clay Richardson from …

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds