I cannot answer questions #1 or #2, but I can speak a bit to question #3. First, a quick overview of protobuf-net.
On the wire, Google's protocol buffers are a messaging format not a serialization format. Messages consist of sequences of key/value pairs, where values can be primitive data types or nested sequences. It's somewhat similar to JSON, except that the keys are integers rather than name strings. The format is language-independent (and thus contains no language-specific type information) and sufficiently self-documenting that values of unexpected properties can be skipped and data types for primitive values can be inferred.
protobuf-net is a contract based serializer for .NET types that serializes from and to the protocol buffer format. "Contract based" means that the serializer builds a map (a.k.a. "contract") between message properties and .Net properties, then uses that contract to construct and deserialize .Net objects. The "root" object to deserialize is specified by the call to deserialize, e.g.
var rootObject = ProtoBuf.Serializer.Deserialize<RootObjectType>(stream)
Basic instructions for its use can be found here and here.
protubuf-net makes only limited use of unsafe code - currently only for efficient deserialization of floating point values I believe. This can be disabled by building with FEAT_SAFE
defined. Thus the possibility of a malformed message overwriting the heap or stack is minimized. Protobuf-net does not share a code base with the Google implementation of protocol buffers.
So, how does protobuf-net construct its contract (the RuntimeTypeModel
) for mapping .Net types to messages? Since message keys are integers rather than strings, the canonical choice of using the .Net public property or field name does not work. Instead, there are the following options:
The type can be annotated with with [ProtoContract]
and [ProtoMember()]
attributes, like this example from the docs:
[ProtoContract]
class Person {
[ProtoMember(1)]
public string Name {get;set:}
[ProtoMember(2)]
public Address Address {get;set;}
}
[ProtoContract]
class Address {
[ProtoMember(1)]
public string Line1 {get;set;}
[ProtoMember(2)]
public string Line2 {get;set;}
}
The properties and fields marked with [ProtoMember]
will be discovered using reflection and serialized. Others will not.
The contract can be constructed programmatically, as shown here.
protobuf-net contract generation is opt-in by default. I.e., only the specified properties and fields are serialized. However, this can be overridden by setting ProtoContractAttribute.ImplicitFields
to one of the following values:
public enum ImplicitFields
{
/// <summary>
/// No members are serialized implicitly; all members require a suitable
/// attribute such as [ProtoMember]. This is the recmomended mode for
/// most scenarios.
/// </summary>
None = 0,
/// <summary>
/// Public properties and fields are eligible for implicit serialization;
/// this treats the public API as a contract. Ordering beings from ImplicitFirstTag.
/// </summary>
AllPublic= 1,
/// <summary>
/// Public and non-public fields are eligible for implicit serialization;
/// this acts as a state/implementation serializer. Ordering beings from ImplicitFirstTag.
/// </summary>
AllFields = 2
}
protobuf-net can also use data contract serializer and XmlSerializer
attributes for automatic contract creation. This can be disabled via the property ProtoContractAttribute.UseProtoMembersOnly
.
While protobuf-net was originally designed as a code-first serializer (you define your types, then define how to serialize them) it does have support for c# code generation from message prototypes. See here for details.
Thus, as you can see, the possibility for unwanted types and code to be injected during serialization is limited by design since the contract, rather than the message, controls the types of constructed objects. This can be further limited via the following recommendations:
Do not use [ProtoMember(DynamicType = true)]
.
protobuf-net implements an extension to the standard allowing the .Net type to be deserialized to be specified in the message itself - breaking the relative safety of of the out-of-the-box standard. This is disabled by default but can be enabled by setting DynamicType = true
. Do not use this functionality, as this is the most obvious way for an unexpected object to be injected during deserialization.
If you are concerned that some other developer on the team might use this functionality (as it does make serializing polymorphic types extremely easy), you can set an event on TypeModel.DynamicTypeFormatting
that throws an exception whenever the serializer tryies to resolve a dynamic type, or a dynamic type name, e.g.:
ProtoBuf.Meta.TypeFormatEventHandler handler = (o, e) => { throw new NotSupportedException("Dynamic typing is not allowed."); };
ProtoBuf.Meta.RuntimeTypeModel.Default.DynamicTypeFormatting += handler;
Be sure to set the event on the typemodel you are actually using, as is pointed out here.
Be careful with inheritance. protobuf-net does support inheritance. Take care not to allow unwanted derived types to creep into the contract. E.g., given the following:
[ProtoContract]
[ProtoInclude(7, typeof(ValidatedSqlQuery))]
[ProtoInclude(8, typeof(DebugSqlCommand))]
public abstract class SqlCommand
{
}
/// <summary>
/// Represents a fully validated, parameterized SQL query
/// </summary>
[ProtoContract]
public class ValidatedSqlQuery
{
}
/// <summary>
/// For testing purposes, execute any command against the database.
/// </summary>
[ProtoContract]
class DebugSqlCommand
{
}
It's possible for a DebugSqlCommand
to get constructed in a List<SqlCommand>
via an appropriate message, possibly causing damage down the line.
Serialize properties, not fields. Do not use ImplicitFields.AllFields
. Avoid using ImplicitFields.AllPublic
whenever possible.
Validate data in all property setters. You can use serialization-specific setters if you don't want to do the validation on the "normal" setters, e.g.:
[ProtoContract]
class Contact {
public string Email { get; set; }
[ProtoMember(1)]
string SerializedEmail {
get { return Email; }
set
{
// Make sure the email is well-formed, throw an exception if not
ValidateEmail(value);
Email = value;
}
}
}
In complex validation scenarios, you can validate multiple related properties in an [OnDeserialized]
callback.
Finally, your deserialization code is only as safe as the objects you deserialize! Do not serialize objects containing or using unsafe code. Do not allow debug objects to be serialized.