I have always been fascinated by .NET’s StackOverflowException. It’s interesting because it’s fundamentally different from most other exceptions—you can’t catch it with a try/catch block. When you overflow the stack, it’s game over—the runtime will terminate your process. This behavior is especially devastating for web services that are deserializing user-controlled data. If your data structure allows recursion, malicious users can easily craft a highly nested payload and use it to DDoS your website.

Serialization libraries have the power to protect you against this type of DDoS attack by limiting the recursion depth during deserialization. That’s exactly what libraries designed with security in mind have always been doing. But they are outliers—most libraries were initially vulnerable to StackOverflowException (and some of them still are). In this post, I’ll review the most widely used .NET serialization libraries and show you how they fare against this mighty enemy. You’ll learn which library versions are safe to use, which serializers require special usage patterns, and which libraries you should simply avoid.

Safe by design

System.Text.Json and Google.Protobuf are the absolute winners. They have never been vulnerable to StackOverflowException, because they have always been enforcing the recursion limit by default. This limit is configurable, though, so nothing can prevent you from intentionally increasing it. But nothing can prevent you from trying to live with grizzly bears, either—it’s just a question of your lifestyle choices.

Previously vulnerable, now safe by default

Newtonsoft.Json is by far the most popular .NET library, with over 2.8B total downloads on NuGet. Despite its enormous popularity, it was only last year that its insecure defaults were fixed (see GHSA-5crp-9r3c-p9vr for more details). The vulnerability was not as bad as it could have been, because you always had the option to control the recursion depth by setting the MaxDepth property in JsonSerializerSettings (though I doubt many people were doing that). ASP.NET Core users were not even at risk— Newtonsoft.Json formatter has always been safe by default. Long story short, you were vulnerable only if you were doing something like this:

T value = JsonConvert.DeserializeObject(s);

If you’ve been keeping your libraries up to date, even this is no longer an issue.

FlatSharp (FlatBuffers implementation) and protobuf-net (Protocol Buffers implementation) were also unsafe by default. Unlike Newtonsoft.Json, these two libraries were 100% vulnerable: there was no option for users to explicitly set the recursion limit. Yours truly discovered these issues and reported them to both library authors. James Courtney and Marc Gravell quickly responded to my reports and immediately published the fixes that made these two libraries safe by default. Huge thanks to James and Marc for keeping the .NET ecosystem safe!

Unsafe by default, but can be configured for safe use

System.Xml.XmlSerializer from the .NET standard library can be used safely, but figuring out how to do that resembles finding a needle in a haystack. If you are learning how to use the library by following the official documentation, you will almost certainly write something like this:

var serializer = new XmlSerializer(typeof(T));
T value = (T)serializer.Deserialize(stream);

Congratulations, you are now vulnerable to StackOverflowException! Let’s say you decide to up your game by using some fancy code quality rules. CA5369: Use XmlReader for Deserialize comes to rescue with the instructions how to securely deserialize XML:

Deserializing untrusted XML input with XmlSerializer.Deserialize instantiated without an XmlReader object can potentially lead to denial of service, information disclosure, and server-side request forgery attacks.

Denial of service is exactly the thing you want to avoid, so you decide to follow this guideline and wrap your input stream in an XmlReader:

using var reader = XmlReader.Create(stream);
var serializer = new XmlSerializer(typeof(T));
T value = (T)serializer.Deserialize(reader);

Sadly, this does not protect you against StackOverflowException at all. At this point, you (justifiably) think it might be wise to revisit your career choices and start raising chickens on a farm. Before you commit to that, you do one final internet search and magically stumble upon the article Security Considerations for Data. It takes 35 minutes to read, doesn’t have any useful code samples, and it’s not even about XmlSerializer. But you are crazy and you read it anyway. By doing so you become a member of an elite group of people who know how to limit the recursion depth when deserializing XML:

var quotas = new XmlDictionaryReaderQuotas { MaxDepth = 32 };
using var reader = XmlDictionaryReader.CreateTextReader(stream, quotas);
var serializer = new XmlSerializer(typeof(T));
T value = (T)serializer.Deserialize(reader);

If you are somehow doing this, congratulations—you are not vulnerable to StackOverflowException! Not only are you safe, but I will also buy you a beer (or a drink of your choice) if we ever meet.

Of course, ASP.NET Core got everything right one more time, because this is exactly how the XmlSerializerInputFormatter works behind the scenes.

So what’s the final verdict for XmlSerializer? If you are using ASP.NET Core to automatically deserialize HTTP requests in XML format, you are safe. Otherwise, you are most likely unsafe and should consider switching to XmlDictionaryReader.CreateTextReader.

MessagePack for C# is the most popular .NET library for working with the MessagePack binary serialization format. It used to be vulnerable to denial of service, but then it got fixed and the vulnerability details were published in GHSA-7q36-4xx7-xcxf (fun fact: I reported the vulnerability to Microsoft Security Response Center while it was still unknown, but I got the response that they are aware of the issue, so no CVE for me this time). Unfortunately, even the fixed version is not safe by default—you have to turn on the secure mode explicitly:

var options = MessagePackSerializerOptions.Standard
  .WithSecurity(MessagePackSecurity.UntrustedData);

T value = MessagePackSerializer.Deserialize<T>(data, options);

The decision of library authors to introduce secure mode and then keep it disabled puzzles me, because all other libraries have chosen the safe-by-default approach to fix this type of vulnerability. It’s not that easy to figure out that this configuration option even exists: it’s in the security section, buried in the middle of a massive README file. But if you care about security, it’s good knowing that at least you have the option of being safe.

Completely unsafe

Ah, Bond. The only serialization library where the recursion limit doesn’t even exist as an option. That wouldn’t be too terrible on its own, because many other libraries had faced the same issue, but ultimately fixed it. The real problem here is that when I reported this to Microsoft Security Response Center, they just didn’t care. Verdict: avoid.

Update (Mar 22, 2023): I might have been too harsh when I said that MSRC didn’t care about my report. Here is the full story: I reported the issue as denial of service in Microsoft Orleans using Bond deserializer. The report was evaluated in the context of Orleans, and since Orleans is not intended to be publicly accessible (as described in the official documentation), MSRC determined that Orleans users are not exposed to denial of service by design.

Summary

If you don’t like reading and you just want me to tell you how to be safe, here’s a pretty table for you.

Library Format Safe version Max depth
Bond Bond none no limit
FlatSharp FlatBuffers 6.3.0 1000
Google.Protobuf Protocol Buffers all 100
MessagePack for C# MessagePack 2.1.90 1 500
Newtonsoft.Json JSON 13.0.2 64
protobuf-net Protocol Buffers 3.10 512
System.Text.Json JSON all 64
System.Xml.XmlSerializer XML all 2 32
  1. Only if you configure MessagePackSerializerOptions.Security property. 

  2. Only if you use XmlDictionaryReader.CreateTextReader

Conclusion

If you keep your libraries up to date or use ASP.NET Core formatters, you are most likely safe from StackOverflowException. Otherwise, you should probably try adopting the guidelines from this post. But how bad would it be to be vulnerable anyway? Stay tuned for the next post, where the real fun begins: I’ll show you how denial of service attack looks like in practice.

Huge thanks to Milica Miljkov for editing this post, and also for making it more about users and less about me.