Files
docview/docs/design/07-security-errors.md
T
2026-09-21 13:41:40 +09:00

167 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 07 セキュリティ・異常系設計
DocView アプリケーション設計書 | 版 1.0 | 2026-09-19
対象要件: R01-R04、R09、R11、R12、R14。文書を開く操作が、原本の変更、文書外のファイル読取り、外部送信、任意プログラム実行へ広がらないようにする。
## 1. 信頼境界
ローカルにあるPDF、HTML、ZIP、EPUBも未信頼入力として扱う。文書の名前・本文・メタデータに書かれた指示をアプリの命令や設定として解釈しない。文書処理から設定ファイルを読み替えたり、拡張コードを自動ロードしたりしない。
| 領域 | 許可する処理 | 禁止する処理 |
|---|---|---|
| Main / FileBroker | UI、検証済み設定、利用者が開いたファイルのhandle管理、限定範囲のIO、IPC検証 | PDF、ZIP、EPUB XML、HTML、CSS、画像の内容解析 |
| PDFWorker | 渡されたPDFの解析・描画、限定的なfont資源利用 | ネットワーク、任意ファイル、原本への書込み、外部起動 |
| ArchiveWorker | 渡されたZIPの索引・展開、EPUB構造XML、font難読化の処理 | 任意パス作成、ネットワーク、XML外部実体、外部起動 |
| WebEngine文書側 | 文書セッション内資源の表示、アプリ管理のDOM位置操作 | 設定・履歴・任意OS API、別文書、遠隔通信、文書JavaScript |
MainがWorkerから受けた索引・ラベル・URL・画像を無条件に信用すると境界が失われる。型、長さ、整数範囲、参照先、文書世代番号を検査し、描画画像についてもstride、幅、高さ、共有バッファ長を照合する。制御IPCの上限は1 MiB、データチャネルは1チャンク8 MiBを初期値とし、詳細は [データ・内部契約設計](05-data-contracts.md) に従う。
## 2. OSによる隔離と配布条件
別プロセスに分けただけではファイルやネットワークへのアクセスを防げない。PDFWorkerとArchiveWorkerにはOSによる制限を設定する。実装前のG0では起動可能性だけでなく、境界の外にある試験ファイルへのアクセス拒否、通信拒否、子プロセス起動拒否、資源上限超過時の停止を実証する。未達の対象OS版は配布しない。
| OS/実行系 | 採用方針 | G0で確かめること |
|---|---|---|
| Linux Worker | seccompとLandlockを組み合わせる。要件を満たせない環境は同等の隔離launcherを利用 | 必要ABI・syscall、font読込み、handle受渡し、全threadへの制限、socket・process作成・root外IOの拒否 |
| Windows Worker | AppContainerを権限境界とし、Job Objectで寿命・資源・子プロセスを管理 | network capabilityなし、必要handleだけの継承、font依存、DACL、停止・再起動 |
| Qt WebEngine | Qt/Chromiumのsandboxを有効のまま利用 | 両OSの配布形態、実際のrenderer隔離、必要なLinux kernel機能、無効化フラグの検知 |
Landlock単独で全種類の通信制限が揃うとは想定せず、socket等の操作はseccomp側でも制限する。Job Object単独もファイル権限の代替にしない。Workerの許可資源は読取り専用文書handle、限定IPC、必要なfont/runtimeのみとし、ホーム全体や任意ディレクトリーを許可しない。[Linux Landlock](https://docs.kernel.org/userspace-api/landlock.html)、[AppContainerの起動](https://learn.microsoft.com/en-us/windows/win32/secauthz/implementing-an-appcontainer)
`--no-sandbox`、`QTWEBENGINE_DISABLE_SANDBOX` 等で保護を外す運用を製品の回避策にしない。sandboxが必要条件を満たさない場合は `E_SANDBOX_UNAVAILABLE` を表示し、該当形式を開かない。Qt WebEngineはrendererのsandboxを提供するが、独立したPDFWorkerやArchiveWorkerの隔離までは提供しない。[Qt WebEngine Platform Notes](https://doc.qt.io/qt-6/qtwebengine-platform-notes.html)
## 3. ZIP・一時資源
### 3.1 検証と書込み
ArchiveWorkerは展開前に索引を検査し、Mainも公開対象entryを再検証する。Mainは検証済みの相対名を文書rootのdirectory handleに結び付け、OSのNOFOLLOW相当の手段でファイルを開く。文字列の前方一致だけでroot内と判定しない。Workerはディスク上の任意展開先を指定できない。
- 絶対パス、ドライブ名、UNC、NUL、`..` セグメント、バックスラッシュ、ADSを作るコロン、Windowsデバイス名を拒否する。
- symlink、hardlink、junction/reparse point、device、FIFOを拒否する。通常ファイルとディレクトリーだけを受け入れる。
- 正規化後の重複、大小文字・Unicode正規化による衝突、ファイルとディレクトリーの衝突、末尾のドット/空白で曖昧になる名前を拒否する。
- アーカイブの所有権、実行権限、ACLを再現しない。ユーザー専用の新しい一時ディレクトリーを作り、他セッションの内容を混ぜない。
- 申告サイズだけでなく、Mainへ届いた実展開バイト数を計測する。各entryのCRC・長さ・完了状態が正常になるまで仮想URLから公開しない。
- 内側のZIPは再帰展開しない。エラー時に展開途中ファイルを残して再利用しない。
これらはZIP Slip、リンクを使う脱出、解凍による資源枯渇を別々に防ぐための方針である。[OWASPアーカイブ試験](https://wstg.owasp.org/latest/4-Web_Application_Security_Testing/10-Business_Logic/09-Upload_of_Malicious_Files/)
### 3.2 上限の初期提案
数値は原要件にはなく、代表書籍と負荷試験で妥当性を確認する設計値である。初期版では06の利用者設定による緩和を提供せず、ビルド時の有限上限とする。値の変更時は安全試験を再実施し、パス・リンクの検査そのものは無効化できない。
| 上限対象 | 初期値 | 判定する場所 |
|---|---:|---|
| ZIP entry数 | 20,000 | Worker索引、Main索引受信 |
| 1資源の実展開量 | 256 MiB | WorkerとMainストリーム受信 |
| 1文書の実展開量 | 2 GiB | WorkerとMainセッション合計 |
| path階層 | 64 | WorkerとMain |
| pathのUTF-8長 | 1,024 bytes | WorkerとMain |
| 膨張率 | 200倍。実展開量32 MiB超のentryに適用 | Workerの入力消費量/出力量 |
| EPUB構造XML 1資源 | 16 MiB | Worker |
| XML深さ/要素数 | 128/200,000 | Worker |
| 同時資源展開 | 2 | スケジューラー |
| 1資源の展開時間 | 10秒で継続表示、30秒で取消 | Main watchdog |
| PDFの1要求 | 30秒で長時間処理を通知、120秒でワーカー停止 | Main watchdog。操作取消は待たずに受理 |
| PDF/Archiveワーカーのメモリー | 1プロセスあたり1.5 GiBを初期の停止上限とする | OS制限とMain監視。通常時の全体1 GiB目標とは別 |
絶対サイズを主要防御とし、圧縮率だけで判定しない。CPU・メモリーはWorkerおよびWebEngineの資源監視でも制限する。WebEngineでの画像デコード前に全ピクセル数を安全に判定できるとは想定せず、rendererの応答監視とメモリー超過時の停止を併用する。WebEngineの停止閾値は初期1.5 GiB/rendererを提案し、監視間隔による瞬間的な超過はあり得る。展開先の空き容量不足は上限超過と別のエラーにする。上限を変更した版での再試行時も入口から同じ検査を行う。
終了時はそのセッションの管理領域だけを削除する。次回起動時の残骸清掃は、所有マーカーと管理下の実体を確認して行う。文書由来のパスを再帰削除の起点にしない。
## 4. HTML・EPUBの隔離
### 4.1 独自URLと資源提供
`doc://<session-token>/...` を `Syntax::Host` で起動初期に登録し、QQuickWebEngineProfileへ専用scheme handlerを入れる。文書ごとのoff-the-record profileを使い、Cookie、localStorage、WebEngine履歴を文書間で継承しない。文書を閉じるとprofileとtokenを破棄する。
`LocalAccessAllowed`、`ContentSecurityPolicyIgnored`、`ServiceWorkersAllowed`、`CorsEnabled`、`FetchApiAllowed` は付けない。`NoAccessAllowed` によって全資源が別originになる構成も使わず、同一文書内のCSS/font等の互換性を保つ。安全なschemeフラグだけに依存せず、handlerはセッションtoken・initiator・method・相対パス・resource kindを照合する。
許可methodは読取要求のみ。空initiatorはアプリが発行した既知のトップレベル要求に限定して受け付ける。文書から別tokenを当てられても、tokenの一致と許可資源集合の両方で拒否する。文書URLのtokenを長期履歴やログへ残さない。
### 4.2 3箇所の制御
| 制御点 | 役割 |
|---|---|
| QWebEngineUrlRequestInterceptor | Chromiumのネットワーク層に届く前に、同一文書の許可資源以外を拒否 |
| QWebEngineUrlSchemeHandler | 実体root・許可entry・MIME・サイズ・寿命を検査し、読取専用資源を応答 |
| WebEngineView.navigationRequested / newWindowRequested | トップレベル遷移、自動遷移、外部ウィンドウ作成を制御 |
request interceptor内で展開完了を同期的に待たない。許可判定を短時間で終え、資源の非同期応答をhandlerへ委ねる。欠落や取消ではrequest jobを失敗として終了し、jobの破棄に合わせてQIODeviceの寿命も終える。
外部HTTP(S)リンクはユーザーが選択したときだけMainへ通知し、宛先を表示したうえで「外部ブラウザーで開く」操作によりOSへ渡す。WebEngine内で外部サイトへ遷移しない。未知のスキーム、OSコマンド、メール送信、file URLを一般のURLとして起動しない。
### 4.3 本文スクリプトとCSP
MainWorldのJavaScriptを無効にし、ApplicationWorldのアプリ固定コードだけで読書位置・見出しを制御する。汎用WebChannelを公開せず、結果は限定されたcallbackから返す。document内の文字列をJavaScriptソースへ埋め込む方法は使わない。
scheme handlerの追加応答ヘッダーでCSPを設定する。基本方針は `default-src 'none'`、`script-src 'none'`、`connect-src 'none'`、`object-src 'none'`、`frame-src 'none'`、`form-action 'none'`。画像・fontは同一文書内、CSSは同一文書内と著者のinline styleを許可する。画像のdata URLはimage用途に限定して許可し、top-level documentやSVG scriptの抜け道にしない。`base-uri 'self'` とナビゲーションgateを組み合わせる。
応答ヘッダーのAPIはQt 6.6以降に存在する。CSPが独自schemeとApplicationWorldで意図どおり動くことをG0で確認し、CSPを迂回するフラグを付けない。CSPを差し込むためにMainで本文HTMLを解析・改変する設計は採らない。[QWebEngineUrlRequestJob](https://doc.qt.io/qt-6/qwebengineurlrequestjob.html)
カメラ、マイク、位置、通知、screen capture、clipboard、file picker、download、protocol登録は文書からの要求を拒否する。DNS prefetch、link auditing、local-file access、local-to-remote access、WebGL、plugins、WebEngine内蔵PDF viewerを無効にする。WebEngineが要求する権限は既定拒否とし、document内の設定で上書きしない。
## 5. PDFに固有の制約
PDFiumはV8とXFAを無効にした構成を基本とする。JavaScript action、Launch action、添付実行、外部自動起動は提供しない。ファイルは読取り専用で開き、PDF保存APIをアプリの操作として公開しない。既存の注釈・フォーム値の静的外観は描画するが、文書へ入力しない。
暗号化PDFのパスワードはその場の入力だけで受け取り、設定・履歴・ログ・クラッシュ情報に残さない。明示的な再入力を許し、誤りを破損ファイルと混同しない。署名が見えるPDFでも、署名検証を実施したと表示しない。
ワーカーへ渡すfontは許可集合に限定する。未埋込みfontに対応するためホーム全体を読めるようにしない。悪意あるfont、巨大画像、再帰的なPDF構造もPDFWorkerの時間・メモリー制限で扱う。
## 6. エラーの共通モデル
エラーは `code / severity / operation / documentGeneration / resourceId / retryable / userMessage / diagnosticId` を持つ。本文や秘密を含む生データをmessageに連結しない。短い説明と復帰操作を主表示とし、詳細は必要時に開く。繰返し発生する欠落資源は件数をまとめ、キー操作を通知で奪わない。
| ID | 表示と復帰 |
|---|---|
| E_OPEN_FAILED | ファイルを開けない。場所・権限を確認し別ファイルを選択 |
| E_FORMAT_UNSUPPORTED | 対応形式でない、またはシグネチャ不一致。元文書へ戻る |
| E_PASSWORD_REQUIRED | PDFパスワードを入力。取消で元文書へ戻る |
| E_PASSWORD_INVALID | パスワードが一致しない。再入力可能 |
| E_PDF_CORRUPT | PDFを解析できない。元文書へ戻る |
| E_PDF_FEATURE_UNSUPPORTED | 動的XFA等の未対応内容。静的部分が読める場合も制限を通知 |
| E_SANDBOX_UNAVAILABLE | 安全な文書処理環境を作れない。該当形式の読込みを停止 |
| E_ARCHIVE_UNSAFE_PATH | root外へ出るパス・リンク・衝突を検出。取込みを停止 |
| E_ARCHIVE_LIMIT | 件数・実展開量・深さ等の上限。項目と上限を提示 |
| E_ARCHIVE_CORRUPT | 索引・長さ・CRCが破損。取込みを停止 |
| E_ARCHIVE_ENCRYPTED | パスワード付きZIPは非対応 |
| E_HTML_ENTRY_MISSING | HTMLの入口がない。入口候補があれば選択 |
| E_RESOURCE_MISSING | 一部資源がない。代替表示で読書を継続 |
| E_RESOURCE_BLOCKED | root外・遠隔資源等を遮断。読める部分を維持 |
| E_EPUB_PACKAGE_INVALID | package/読書順が確定できない。読込みを停止 |
| E_EPUB_NAV_INVALID | nav破損。NCXまたは章一覧へfallback |
| E_EPUB_SPINE_MISSING | 該当章が欠落。他の章へ移動可能 |
| E_DRM_UNSUPPORTED | 未対応の暗号化・保護方式。解除処理は行わない |
| E_LOCATOR_UNRESOLVED | 正確な位置に戻れない。近い位置へ戻ったことを通知 |
| E_WORKER_TIMEOUT | 処理期限超過。取消/再読込み/別文書 |
| E_WORKER_CRASH | Worker停止。UIを維持し、利用者の明示操作で再試行。繰返す停止ではセッションを閉じる |
| E_STORAGE_FULL | 一時領域・状態保存先の空き不足。展開を止めて清掃 |
| E_CONFIG_INVALID | 設定エラー。現在の有効設定を維持 |
| E_STATE_SAVE | 読書状態を保存できない。閲覧を継続し保存失敗を通知 |
ファイル切替中の失敗は、コミット前なら元文書と元位置を維持する。操作取消は通常結果として扱い、エラー通知を出さない。深刻な境界違反や繰返すクラッシュでは再試行ループに入らず、その文書セッションを閉じる。
## 7. 診断情報・更新
ログは時刻、エラーID、操作、件数、処理時間、依存版を中心とする。本文、パスワード、URL token、任意のローカル絶対パスを既定ログへ記録しない。必要なファイル名も利用者の診断操作で確認できる範囲に留める。クラッシュレポートや利用統計を自動送信しない。
Qt、Chromium、PDFium、ZIP/XMLライブラリーの依存一覧と版をリリースごとに残す。脆弱性修正時は内容解析器を優先して更新し、PDF描画の回帰と文書隔離試験を行う。更新で無効になった保護を性能改善の名目で省略しない。
## 8. 必須の境界試験
受入試験に、path traversal、symlink、大小文字衝突、ZIP bomb、偽の申告サイズ、CRC不一致、XML実体、CSS外部import、外部font、script、iframe、form、meta refresh、file URL、別token、二重percent、Worker偽応答、サイズ不正画像を含める。
期待結果はUIが落ちないことに加えて、許可外ファイルの読取り・書込み・送信・プログラム起動が発生しないこと。G0では監視用ファイル・通信先を用いて観測する。通常のCSS相対参照やIDPF難読化fontも同時に試験し、防御が正当な書籍を不必要に壊さないことを確認する。
## 9. 参照資料
確認日: 2026-09-19。制限値と配布ゲートはDocView独自の設計判断である。
- [QWebEngineUrlRequestInterceptor](https://doc.qt.io/qt-6/qwebengineurlrequestinterceptor.html) - ネットワーク層より前の要求制御。
- [QQuickWebEngineProfile](https://doc.qt.io/qt-6/qquickwebengineprofile.html) - 文書用profileとhandler登録。
- [QWebEngineUrlScheme](https://doc.qt.io/qt-6/qwebengineurlscheme.html) - originと権限フラグ。
- [QWebEngineSettings](https://doc.qt.io/qt-6/qwebenginesettings.html) - 文書スクリプト・local資源等の設定。
- [WebEngineView](https://doc.qt.io/qt-6/qml-qtwebengine-webengineview.html) - 遷移・権限・renderer停止の通知。
- [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html) - 展開後サイズの制限。