1. シナリオと脅威モデル
アナリティクスサービスは外部テナントから Arrow IPC ストリームを受信し、それらを Python、Rust、Java のコンシューマに渡します。攻撃者は、不整合なスキーマ、極端に大きな長さ、範囲外のオフセット、再帰的なネスト、悪意のある辞書、またはレシーバのバジェットを超えるバッファを作成する可能性があります。目標は、パースの失敗が現在のリクエストにのみ影響するようにしながら、バッチスループットを確保することです。
まず信頼境界を定義します。ネットワークバイト、IPC メタデータ、バッファの内容、およびビジネススキーマは信頼できません。認証されたテナントであっても、データが自動的に安全であるとは限りません。セキュリティモデルは、単一の言語バインディングのデフォルトのみに依存するのではなく、Arrow Columnar Format、C Data Interface、および IPC をカバーする必要があります。
2. Arrow のセキュリティ前提条件を明示する
カラムナー形式は、バッファ、長さ、オフセット、null ビットマップ、辞書、およびネストされたレイアウトを記述します。ゼロコピーはコピー処理を削減しますが、コンシューマが外部入力によって記述されたメモリ領域を読み取ってしまう可能性があります。安全な実装では、データをコンピュートエンジンに渡す前に、実際のバッファに対してメタデータを検証します。
有効なフォーマット仕様と検証済みの入力を同一視しないでください。プロトコルバージョン、拡張型、言語実装にはそれぞれ異なる境界があります。サポートするバージョンと型のセットを固定し、未知のフィールドに対する明示的なポリシーを定義します。
3. スキーマ、長さ、オフセットの検証
第 1 段階では、制限されたメタデータのみをパースし、フィールド数、ネストの深さ、データ型、辞書参照、およびバッチの行数制限をチェックします。各バッファについて、offset + length がオーバーフローせず、実際の割り当て内に収まっていることを検証します。可変長の文字列やリストについては、値バッファの内部に収まる単調増加するオフセットを検証します。
if offset < 0 or length < 0: reject
if offset > buffer_size: reject
if length > buffer_size - offset: reject
if nesting_depth > MAX_DEPTH: rejectオーバーフロー安全な算術演算を使用し、負の整数や非現実的に大きな整数、スキーマと互換性のない null 数を拒否します。検証前に攻撃者が宣言した長さを決して割り当てないでください。
4. リソースバジェットとバックプレッシャーの適用
メタデータバイト数、カラム数、行数、総バッファバイト数、ネストの深さ、辞書サイズ、および CPU 時間について、リクエストごとのバジェットを設定します。バジェットを各バッチパーサーに渡し、それを超えた場合はリクエストを直ちにキャンセルして割り当てを解放します。
アップロード全体を一度にメモリにロードするのではなく、IPC ストリームに対して制限付きの読み取りとバッチバックプレッシャーを使用します。小さな入力が大きな割り当てにならないように、圧縮、辞書、および反復参照からの展開を制限します。1 つの悪意あるリクエストがすべてのワーカーを消費しないように、テナントごとにクォータを分離します。
5. ゼロコピーを維持するタイミングとコピーするタイミングの判断
読み取り専用のゼロコピーを維持するのは、バッファの所有権、ライフタイム、および境界を証明し、ダウンストリームのコードがそれを変更できないことを確認した後に限られます。解放、再利用、または書き込みが行われる可能性のあるネットワーク受信バッファ、一時的な mmap、または言語間 FFI メモリは、制御されたアリーナにコピーする必要があります。
コピーは失敗ではありません。信頼できないライフタイムを制御されたライフタイムに変換するセキュリティ境界です。カラムごとに選択します。検証済みの数値バッファは共有し、文字列、辞書、またはネストされたカラムはバジェットが許す場合にコピーします。「完全なゼロコピー」の背後にリスクを隠すのではなく、コピーされたバイト数とレイテンシを記録します。
6. パース、エラー、および互換性の分離
CPU、メモリ、ファイル記述子、および実経過時間(ウォールタイム)の制限を設定した、制約のあるワーカーまたはプロセス内でパーサーを実行します。サポートされていないバージョン、スキーマの不一致、オフセットの範囲外、またはバジェット超過などの構造化された理由を返します。生のペイロードや内部アドレスを決してそのまま返してはいけません。
拡張型、未知のメタデータ、および将来のフィールドには許可リスト(allowlist)を使用します。互換性のために、クエリエンジンに入る前に内部スキーマに変換します。Arrow 25 や言語バインディングをアップグレードする前に、同一の悪意あるコーパスを差分テストとして実行し、エラークラス、ピークメモリ、および結果を比較します。
7. 監視、ファジング、インシデント対応
テナント、フォーマットバージョン、スキーマハッシュ、拒否理由、入力サイズ、パース所要時間、ピークメモリ、コピーされたバイト数、およびキャンセルを記録します。機密コンテンツをログに記録する代わりにペイロードをハッシュ化します。拒否クラスごとにアラートを設定し、設定ミスのあるクライアントと継続的な攻撃を区別します。
AddressSanitizer、MemorySanitizer、または同等のツールを使用して、ランダムなスキーマ、オフセット、null ビットマップ、辞書、および切り詰められたストリームをファジングし、クラッシュや範囲外アクセスを検出します。最小化された再現コードを保持し、修正後にリグレッションテストを実行します。本番環境の異常に対しては、まずテナントまたはフォーマットバージョンを分離し、次にサンプルを分析して影響を受けた認証情報をローテーションします。
8. ルーブリックとフォローアップ
説明が必須の項目
- Arrow のメタデータ、バッファ、およびビジネススキーマを信頼できないものとして扱い、長さ、オフセット、深さ、辞書参照を検証する。
- ゼロコピーを無条件の目標として扱うのではなく、バジェット、バックプレッシャー、および分離によってメモリと CPU を制御する。
- コピー境界、構造化エラー、ファジング、可観測性、およびアップグレード戦略を提供する。
フォローアップの質問
- 文字列カラムのオフセットは単調増加していますが、最後の値がバッファを超えています。どのレイヤーでそれを拒否しますか?
- 辞書やネストされたリストが無制限のリソースコストへと展開するのをどのように防ぎますか?
- 1 つの悪意ある入力に対して、Python、Rust、Java のバインディングが同じ結論に達することをどのように証明しますか?
採点基準
優れた回答は、フォーマットの検証、リソースガバナンス、メモリの所有権、およびランタイムのエビデンスを結びつけます。不可能なレイアウトを拒否し、バジェット内でバッチを消費し、分離、ファジング、メトリクスを用いて信頼できない入力がサービス全体の障害になり得ないことを証明します。