-
Notifications
You must be signed in to change notification settings - Fork 0
MS_DotNetSerialize
-
以下の様な様々なシリアライザも存在するが、
ココでは、BinaryFormatter のバイナリシリアライズのみ取り扱う。 -
System.Runtime.Serialization 名前空間の BinaryFormatter クラスと共に使用する。
補足(最重要・現在の結論を先に): 本ページの主題である
BinaryFormatterは、現在の .NET では使ってはならない。
バージョン 状態 .NET Framework 使用可能(ただし非推奨) .NET 5 Obsolete(警告)、ASP.NET Core では既定で無効 .NET 8 使用すると既定で実行時例外 .NET 9 以降 実装ごと削除( PlatformNotSupportedException)理由は後述のセキュリティの通り、
デシリアライズ時に任意コード実行を許してしまう設計上の欠陥にある
(修正不能と判断され、廃止された)。本ページは、既存資産の理解と移行判断のための記録として残す。
新規開発ではSystem.Text.Jsonを、
バイナリが必要なら MessagePack / Protocol Buffers を選ぶこと。
BinaryFormatter のバイナリシリアライズを使用すると、以下のような処理を実装できる。
- オブジェクトをバイトに変換できる。
- オブジェクトをバイトから復元できる。
- ファイルにセーブ&ロードできる。
- ネットワーク経由で転送できる。
-
.NET Tips (VB.NET,C#...)
- オブジェクトの内容をバイナリファイルに保存、復元する
https://dobon.net/vb/dotnet/file/binaryformatter.html
- オブジェクトの内容をバイナリファイルに保存、復元する
-
.NET 5への移行で、obsolete and prohibited に。
補足(
BinaryFormatterが便利だった理由): 他のシリアライザと違い、
「型を書かなくても、オブジェクト グラフを丸ごと保存できる」
という点が魅力だった。// 属性を付けるだけで、参照関係も循環参照も含めて丸ごと保存 [Serializable] class Node { public Node Next; public string Name; }
- private フィールドも保存される
- 循環参照を扱える(参照 ID で管理)
- スキーマ定義(IDL)が不要
しかし、この「型情報をデータ側が持ち、それを信じて復元する」
という仕組みそのものが、後述の脆弱性の正体でもあった。移行先としては、
元の用途 移行先 設定・状態の保存 System.Text.Json高速・小サイズが必要 MessagePack、Protocol Buffers プロセス間・キャッシュ MessagePack、 DataContractSerializer完全な複製(ディープ コピー) 手書き / ソース ジェネレーター(シリアライズを流用しない)
以下の様なシリアライザも存在する。
- DataContractSerializer
- MsgPackFormatter - MessagePack
- XmlSerializer
- DataContractSerializer
-
純正
- DataContractJsonSerializer
- JsonSerializer - System.Text.Json
-
その他
- JsonSerializer - JSON.NET
- JsonSerializer - JSON.NET BSON
補足(現在の選択指針): 上記は網羅的だが、
現在の実務では選択肢はかなり絞られる。
シリアライザ 現在の位置づけ System.Text.Json標準・第一候補。ソース生成で AOT 対応、高速 Json.NET (Newtonsoft) 既存資産・高度なカスタマイズが要る場合のみ MessagePack for C# バイナリの第一候補。非常に高速 Protocol Buffers gRPC を使うなら必然 XmlSerializerSOAP / 既存の XML 連携 DataContractSerializerWCF 由来。新規では選ばない DataContractJsonSerializer事実上、役目を終えた BinaryFormatter使用禁止(.NET 9 で削除)
System.Text.Jsonは当初 Json.NET より機能が少なかったが、
現在は多態デシリアライズ、required、カスタム コンバーター等が揃い、
移行できない理由の方が少ない。
相互運用性あり
相互運用性あり(ポインタだけ注意)
以下の検証コードで検証した結果、基本的に相互運用性はありそう。
-
net ⇔ netcoreapp
Windows 上であれば動作する。 -
netcoreapp(on Windows) ⇔ netcoreapp(on Linux)
netcoreapp 同士であれば動作する。 -
net ⇔ netcoreapp(on Linux)
netcoreapp(on Linux) → net の場合に限り動作しないケースがあった。
(デシリアライズした Dictionary に IConvertible が無いとかそういう例外)。
補足(なぜ壊れやすいのか):
BinaryFormatterの出力には
アセンブリ修飾された型名(System.Private.CoreLib等)が埋め込まれる。.NET Framework → mscorlib, Version=4.0.0.0, ... .NET Core / .NET → System.Private.CoreLib, Version=...このため、
- アセンブリ名が違うと復元できない(型解決に失敗する)
- 内部フィールドが変わると復元できない
(Dictionary<K,V>の内部実装は .NET Core で変更されている)原文が遭遇した「Dictionary に IConvertible が無い」という例外は、
まさにこの内部実装差によるものと考えられる。要点:
BinaryFormatterの出力は
「同じランタイム・同じバージョンで読む」ことが前提であり、
永続化フォーマットとして使うべきではなかった。
ファイルや DB に保存したBinaryFormatterの出力は、
ランタイム更新のたびに読めなくなるリスクを抱える。
-
一般的に遅いとされる。
-
ゲーム業界では、MessagePackなどの、
POCO シリアライズに特化した高速なシリアライザが開発されている。
補足(遅い理由):
BinaryFormatterは
すべての型をリフレクションで走査し(.NET の Reflection)、
型名文字列や参照 ID をデータに埋め込むため、
速度・サイズの両面で不利である。一方、MessagePack for C# などは
事前にフォーマッターのコードを生成しておくことで
実行時のリフレクションを排し、桁違いの性能を出している。
「バイナリだから速い」のではなく、
コード生成しているから速い、という点が本質である。
最近、問題視されてきている模様
(オープンなサービスなどで、不特定多数ユーザから
バイナリデータをインジェクションされるのが脅威)。
補足(脆弱性の仕組み/最新化): 原文の懸念は的中し、
これがBinaryFormatter廃止の決定打となった
(CWE-502: Deserialization of Untrusted Data)。なぜ危険か:
① データ側に「復元すべき型名」が書かれている ② BinaryFormatter は、それを信じて型を読み込み、インスタンス化する ③ 復元の過程で、その型のコンストラクタや IDeserializationCallback、finalizer などが動く ④ 攻撃者は「復元されるだけで危険な処理が走る型」を指定できる → 任意コード実行(RCE)④ で使われる既存の型の連鎖を ガジェット チェーンと呼び、
.NET 標準ライブラリの中にも利用可能なものが多数見つかっている。決定的なのは、「入力を検証すれば防げる」わけではない点である。
型を復元する前に中身を検査することが原理的にできないため、
Microsoft は修正ではなく廃止を選んだ。同じ理由で、次のシリアライザも同様に危険とされ、
非推奨・削除の対象になっている。
BinaryFormatterSoapFormatterNetDataContractSerializerLosFormatter/ObjectStateFormatter(ASP.NET の ViewState)なお、
JsonSerializer系でも
型名をデータに含める設定(Json.NET のTypeNameHandling.Allなど)を
有効にすると同じ脆弱性が発生するため、
「JSON なら安全」ではない点に注意。
- neue cc - .NET(C#)におけるシリアライザのパフォーマンス比較
http://neue.cc/2010/05/29_261.html
詳細はコチラ
- BinaryFormatter のセキュリティ ガイド
https://learn.microsoft.com/ja-jp/dotnet/standard/serialization/binaryformatter-security-guide - BinaryFormatter は .NET 9 で削除されました
https://learn.microsoft.com/ja-jp/dotnet/core/compatibility/serialization/9.0/binaryformatter-removed
-
クロスプラットフォーム&高性能なシリアライザ
-
双方とも、JSON のバイナリ版(CBOR)みたいなものだが、
-
少々、思想が異なる。
端的に言うと、-
- gRPC 用に設計されている。
- 運用を見据えるなら Protocol Buffers。
-
- ビッグデータ用のユーティリティとして設計されている。
- 手軽に使いたいなら MessagePack。
-
-
Google によって開発され、主に、gRPC で使用される。
-
Protocol Buffersは IDL を使用するので、
- データの構造化に重きを置いていると言える。
- クロスプラットフォーム対応が完全になり得る。
-
serialize/deserialize の速度とデータ長の圧縮をアドバンテージとしている。
-
IDL が無いので、クロスプラットフォームが保証されない部分がある模様。
-
なんとなく、CBOR的(CBORから参照されている)で、
IETF 標準化が提案されたが、下位互換を重視するため
標準化を拒否した等の、開発のポリシーが読み取れる。
(開発者がビッグデータ界隈の出身である事を考えると解り易い)。 -
参考
- MessagePackのIETFへの提案に関する困惑 - たごもりすメモ
https://tagomoris.hatenablog.com/entry/2013/03/01/130433
- MessagePackのIETFへの提案に関する困惑 - たごもりすメモ
補足(.NET で使う場合の実際): 原文の整理は現在も有効だが、
.NET 実装としては次の点を補っておく。
MessagePack for C# protobuf(Google.Protobuf) スキーマ 不要(属性を付けるだけ) .protoが必須生成 ソース ジェネレーター(AOT 可) protocでコード生成速度 非常に速い(.NET 最速級) 速い 他言語連携 実装依存の部分あり 確実 版数互換 Key の番号付けで対応 フィールド番号で堅牢に対応 判断の目安:
- .NET だけで完結し、速度が欲しい → MessagePack for C#
- 他言語と連携する、gRPC を使う → Protocol Buffers
- スキーマ進化を長期に管理したい → Protocol Buffers
-
.NET でのシリアル化
https://learn.microsoft.com/ja-jp/dotnet/standard/serialization/- バイナリ シリアル化
https://learn.microsoft.com/ja-jp/dotnet/standard/serialization/binary-serialization - System.Text.Json の概要
https://learn.microsoft.com/ja-jp/dotnet/standard/serialization/system-text-json/overview - System.Text.Json のソース生成
https://learn.microsoft.com/ja-jp/dotnet/standard/serialization/system-text-json/source-generation
- バイナリ シリアル化
-
シリアル化
-
BinaryFormatter Class (System.Runtime.Serialization.Formatters.Binary)
https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter
Tags: 移行, プログラミング, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。