by shigemk2

当面は技術的なことしか書かない

BSDカーネルの設計と実装 読書会 (34) まとめ #readDaemon

connpass.com

おさらい

www.shigemk2.com

今回

13.10.3 鍵管理

  • ユーザレベルアプリケーションはUDPやTCPなどのトランスポートプロトコルを扱うような方法でIPSecを利用することが出来ない

IPSecで使用する鍵の自動配布はインターネット鍵交換(IKE)プロトコルによって行う

  • デーモンの話はしない
  • ユーザーレベルアプリケーションはPF_KEYの型を持つソケットを使ってセキュリティデータベースにアクセス
  • SAの期限切れなど、セキュリティデータベースの状態が変化すると、アプリケーションはその情報をKEYソケットからメッセージとして読みだす
  • データベースに対するコマンドを送る
  • アドレスプロトコルファミリを指定してデータを送信するとルーティングソケットを通す

  • ROUTINGソケットを通すと暗号化されるという話だろうか

  • 突然鍵の話出てきたけど暗号化の話が出てきたからではなかろうか。

KEYソケットメッセージ

APIっぽい f:id:shigemk2:20150523142606p:plain

KEYソケットの構成

  • SADBではじまる基本メッセージの集合
  • SADB_Xで始まる拡張メッセージの集合

KEYソケットメッセージの構成

  • データを取得するやりとりをするメッセージ

  • ベースヘッダと拡張ヘッダによる

  • ベースヘッダには共通情報が含まれる
  • あと、パディングが挿入されていればそのすべてを含むメッセージ全体の長さが格納される
  • 受付プロセスに対してカーネルからメッセージを送るときには、PIDに0を設定する
  • カーネルからプロセスへの返信するときのプロセス

セキュリティアソシエーションデータベースとセキュリティポリシーデータベース

  • ベースヘッダだけでは変更できなくて、変更するためにはアプリケーションはメッセージに1つ以上の拡張ヘッダを付加する必要がある

f:id:shigemk2:20150523143233p:plain

  • 認証や暗号化のアルゴリズムを設定するときなどに使用される

  • セキュリティアソシエーションは1対1の関係なのか 関係ごとに別々のアルゴリズムを設定したりするかも

  • sadbはセキュリティアソシエーションデータベースの略。。
  • 1対1で確立されているから、それを管理するのがSADB(データベースってなんだったけっていうから、そういう関係を管理するDBなのかも)

アソシエーション拡張

  • アドレス拡張もともに指定する必要がある
  • IPv4とIPv6アドレスをsockaddr構造体として格納(4で6でも受け入れ可能)

PF_KEY実装の問題

  • データグラムプロトコルのため、メッセージ長が64Kバイトに制限される
  • クライアントひとつひとつに対してアソシエーションを構築している? 社員一人ひとりにセキュアな関係を構築するために、大企業レベルだと数百または数千のセキュリティアソシエーションが同時に利用される(ヒューリスティックに見てるか、人海戦術か)
  • ユーザーレベルデーモンの実装が困難

KEYソケットの目的

  • カーネル内に格納されたセキュリティアソシエーションデータベースを管理すること
  • ローカルのDBにアクセスするため?
  • セキュリティアソシエーション構造の実体はC言語上のオブジェクト
  • それを操作するための一連の関数をもっている

セキュリティアソシエーションデータベース

  • 構造体の双方向リンクリストとして管理
  • SAはlarval, mature, dying, deadの4つの状態をもっている
  • SAの有効期限が過ぎるとdyingになる(仮死状態)が、復活できる
  • deadはもうダメ

↓SA構造体の様子 f:id:shigemk2:20150523144250p:plain

セキュリティアソシエーション構造体

  • 使用アルゴリズム SPI 鍵データなどSAにかんするすべての情報を保持する
  • 有効期限フィールドは設定しなければ期限は切れないが、設定することが推奨される
  • ハードリミットとソフトリミットの2つがある

それぞれセキュリティアソシエーション構造体

  • 過剰な抽象化
  • パケットを処理する関数を管理するためにいくつかの関数テーブルをもっている
  • tdb_xformなど
  • いろいろなテーブルがあるけど、実際の処理を隠蔽するため
  • 保守のためにこれらのインタフェースは継承するよういなった
  • 新しいプロトコルや暗号関数の追加が簡単に。

KEYソケット

  • 他の型のソケットと同じように実装
  • keydomain keysw key_usrereqs key_output() など
  • 接続指向的な方法だとEINVAL(Error)を返す

アプリケーションがKEYソケットに対して書き込み

  • 最終的にkey_output()関数によって処理
  • エラー検査のあとに、メッセージをkey_parse()に送る
  • より詳細なエラー検査のあとに、key_typesという関数ポインタスイッチを通して実行

  • key_typesを通じて実行→中抜き

  • DBとのアクセスの話をしていると思われる
  • 鍵の管理はソケットを通して通信しているというスタイル?
  • SA構造体はカーネルのデータ構造
  • ヘッダやアプリケーション拡張はソケットに通信するための構造体

セキュリティデータベースに更新

  • カーネルが受付中のアプリケーションにメッセージを送る場合は、key_sendup_mbuf()関数を使う

13.10.4 IPSecの実装

  • IPSecプロトコルは、IPv4とIPv6の往路とコルスタックが関与するパケットの処理のすべての領域に影響を与える
  • IPv4における3つの経路
  • 内向き
  • 外向き
  • 転送

IPSecの処理が加わることで通常のパケットhソリが影響を受ける点

  • 一部のパケットを複数回処理しなければならない
  • 通常のTCPやUDPの処理とは性質を異にする

継続制御型のパケット処理

  • IPSecソフトウェアがパケットタグを多用することの1つの理由
  • ハードウェアアクセラレータ 結果のパケットをプロトコル・スタックに送って最終的に待機しているソケットに配送
  • ヘッダやデータ部分に格納することは不可能
  • 完了情報をパケットヘッダで管理するのは明らかなセキュリティホール
  • パケットタグのほうがパケットにメタデータを追加する方法としては柔軟
  • IPSecで使用するタグが下の図

f:id:shigemk2:20150523152216p:plain

  • どこに情報が格納されているのか
  • 暗号化 復号化をやるチップがNICに載っている
  • どこかしらにタグが保存されている

カーネルが受信したIPv4パケット

  • ip_input()で処理
  • そこで2つの検査を行う
  • パケットがトンネルの一部かどうか(中継の間だけIPSecで)
  • パケットを転送するときに行う(ルータは転送するパケットに関するセキュリティポリシーを実装することが出来る)
  • おーけーだったらポリシー関数に渡して処理。拒否だったらエラーを通知しないでパケットを破棄

ip_input()によって、そのパケットが有効なもので、ローカルの計算機宛に送られたものと判断されると、プロトコルスタックフレームワークがあとの処理を引き継ぐ

  • 適切な入力関数に送られる
  • SPIを使って、そのパケットに対するSA構造体を取得
  • 関連するmbufを整える
  • mbufはメモリ管理機構で中心的に構成されているもの
  • なお、ディスク用のバッファはuio

アプリケーションはインターネット上の他のホストを通信するのにIPSecが使われていることを意識しない

  • 出力するパケットはip_output()関数で制御
  • パケットに適用するセキュリティポリシーが制御されているかどうかを調べる
  • セキュリティプロトコルの出力関数は、適切な暗号関数を利用してパケットを送信できるかたちに変換
  • 適切に変換されたらPACKET_TAG_IPSEC_OUT_DONEというタグを付与
  • (送信時には明示的なタグがつけられているのに受信時にはつけられていないようにみえる)

キーワード:継続制御型

関数を最後まで実行して戻るのではなく、2つあるいはソレ以上の関数がお互いを呼び合いながら協調的に動作するプログラミングのスタイル。(FPでいうところの継続渡し)

お気楽 Scala プログラミング入門

  • 同じ関数が2回呼ばれるから情報を共有しないといけない。その情報がタグなので、タグがないといまどういうステータスなのか分からない
  • ここでは、ペイロードにそういう情報を入れるのだろうか

  • TCPのしたにIPv4?

  • IPv4のうえにIPSecのうえにTCP?
  • このあたりはパケットを読まないとわからないかも。。。

13.10.5 暗号サブシステム

  • IPSecが提供するすべてのセキュリティプロトコルは、暗号機能をサポートするAPIとライブラリの上に構築されている
  • FreeBSDでは対称と非対称の両方の暗号機構をサポート
  • 対称鍵暗号(暗号化と復号化が同じ鍵) vs 非対称鍵暗号(暗号化と復号化が違う鍵)
  • ここでは対称鍵暗号の実装方法について述べる

暗号サブシステムは完全にプリエンプティブ

  • 一般的にはマルチタスクの話で、コンピュータが実行中のタスクを一時的に中断する動作であり、基本的にそのタスク自体の協力は不要で、後でそのタスクを再実行する
  • 途中で一旦止めて、また再開できる仕組みのこと

プリエンプション - Wikipedia

  • ソフトウェアドライバとハードウェアドライバは完全に同一視できる
  • すべて共通のAPIが提供

暗号サブシステムは2つのAPI集合と2つのカーネルスレッドによって実装

  • 計算モデルはジョブサブミッションとコールバックに基づく
  • キューに投入すると同時に作業が完了したときに関数のポインタを登録(継続渡しとよばれる)
  • ジョブサブミッション (作業を中断してデータを送る)

暗号機能の利用者は暗号サブシステムに作業を依頼する前にセッションを確立する

  • セッションは、ユーザが要求しようとする作業の種類に関する情報をカプセル化するための作法を提供
  • デバイスが消費する資源の量を制御
  • デバイスによっては同時にサポートできる作業量が制限
  • crypto_newsession()関数を実行すると有効なセッション識別子かエラーか返される

適切なセッション識別子の取得

  • →暗号記述子を要求できる
  • 記述子の準備ができるとcrypto_dispatch()関数を使って暗号サブシステムに渡して処理のキューに投入。作業が完了するとコールバック関数が実行される

f:id:shigemk2:20150523155313p:plain

  • エラーが発生するとコールバックに渡される暗号記述子のcrp_etypeフィールドにエラーコードが設定される

特殊用途の暗号ハードウェアへの低レベルインタフェースを提供

  • 一連のハードウェアドライバが用意されている
  • 登録時に3つの関数ポインタを提供

f:id:shigemk2:20150523155517p:plain

  • newsession() crypto_newsession()実行時に呼ばれる
  • freesession() crypto_freesession()実行時に呼ばれる
  • crypto_proc()から実行

暗号化サブシステムの仮想部分

  • 2つのソフトウェア割り込みと2つのキューを使用
  • この時のキューはジョブサブミッションで暗号化を要求するときのキューなのでは
  • 関数実行はcrypto_proc()→crypto_invoke()→crypto_done()の順
  • まれなケースではキューに入れずに直接コールバックを実行することがある
  • 割り込みを禁止した状態でユーザのコールバックを実行するとシステムの対話的性能が低下する
  • コールバックをキューにおいて、あとからソフトウェアの割り込みスレッド crypto_ret_procによって実行される

このシステムの問題

  • 複数のスレッドを使用すると暗号処理のたびにコンテキストスイッチが2回発生するため、スループットが著しく低下する
  • いくつかのコールバック関数はほとんど処理を行わないので、高価なコンテキストスイッチを無用に増やす
  • 処理の依頼キューは操作をバッチ的に処理するが、バッチ型の処理を利用者は必要としないので、キューを通して作業を分配するのは不要なオーバーヘッド

  • 1個できたら1個やってっていう処理をやっているので、無用な待ち時間が発生する

性能上の問題を解決するために

  • いくつかの修正
  • 作業をバッチ化するかどうかを決めて、しない場合はキューによる処理を完全にバイパスして直接コールバック関数を呼ぶ
  • 負荷が軽い暗号処理についてはキューを使わずに直接実行(crypto_req_qキューをバイパスして最適化)

  • cryptoはクリプトと読む。

  • crypto_ret_qとcrypto_req_qって名前がちゃうのか ソースコードにはcrp_ret_qはあるけど。。。おそらく正しいのはcrp_ret_q

なお:暗号化はソフトにすると解析されるからチップにすることはよくある

  • ブルーレイとかはHDCPとかHDMIとかいろいろ経由しないと見れない
  • ソフトには鍵がかかっている
  • ビデオカードのところで暗号化して復号化するっていうところでなんかしている
  • AACSはデコーダーチップがグラフィックボードに乗っかっている
  • メモリの上には復号化されたデータは残らない

14 起動と終了

計算機の電源が投入されたばかりの状態ではCPUは何も実行していない。

  • バイナリイメージを記憶装置からメモリにロード。
  • 不揮発性の記憶装置に格納されたプログラムを実行
  • ロードしたプログラムの先頭から実行
  • これらの処理はブートストラップとよばれる

  • コールドスタート状態からユーザーモードプログラムが実行可能な段階まで移行する過程について

14.1 概要

  • とはいってもカーネルは単なるプログラム
  • ファイルシステム上にバイナリイメージがファイルとして保管されている
  • ルートファイルシステム

f:id:shigemk2:20150523164939p:plain

  • bootという名前のついた特別なプログラムをロードして実行する

実行を開始したbootプログラム

  • /boot/kernel/kernelをロードしてカウントダウン
  • コンソールを通してユーザーにコマンド行インタプリタのプロンプトを表示してユーザーの入力を受け付ける

カーネルはbootコマンドを実行することで起動

ながれ。

  • CPUを初期化
  • 仮想アドレス変換を無効
  • ハードウェア割り込みを禁止

bootプログラムによってロードされたFreeBSDカーネル

  • いくつかの初期化のための処理を行い、システムを通常の運用状態へと移行する準備する
  • CPUの初期設定を設定
  • 仮想記憶マップを有効
  • 機種依存、機種非依存の初期化作業をおこなう
  • 機種非依存の設定を終了すると、システムは実行可能な状態になる。

メモ: bootは結構複雑なプログラム

ディスク構成

  • MBR(IPLが入っていて、BIOSがMBRを読む メモリのE8000にROMがあって、それがBIOS。E7000にIPLがある)
  • ディスクにはパーティションがあって、ブートローダがパーティションの先頭があって(PBR)、MBRがPBRを呼び出し、PBRがbootプログラムを呼び出す
  • bootは最低限の設定だけを行い、FreeBSDカーネルを呼び出す
  • 以上のことをおこなったうえで、カーネルの読み込みが始まる

  • MBRは機種非依存

  • 多段ロケットのイメージ
  • Forthはbootが解釈できるインタプリタ。bootのなかにForthが入っている。Forthは誰も書かないから、Luaになるかも?みたいな流れはある

  • UEFIから直接bootを起動できるので、BIOSを使わずにUEFIアプリケーションをごにょごにょする界隈があったりする。

14.2 ブートストラップ

  • プログラムのブートストラップは機種依存の処理
  • 処理はBIOSによって行われる
  • BIOSは不揮発性の記憶媒体
  • CPUがリセットされると自動的に実行
  • BIOSはハードウェアがリセット時に正常に機能することを確かめるための診断機能をもっている

14.2.1 bootプログラム

BIOSはFreeBSDのファイルシステムの構造を理解しない

  • 他のスタンドアロンプログラムをロードして実行するための汎用のスタンドアロンプログラム(FreeBSDの助けを借りずに実行することの出来るプログラム)
  • BIOSからアクセス可能なシステムディスクの先頭数セクタの領域に格納

bootプログラムがロードされ実行

  • 実行イメージをファイルからロードしてロードしたプログラムの実行を開始する
  • ブートプログラムはデフォルトのデバイスとプログラムを知っている
  • bootの実行時スタックを初期化
  • パラメータを格納
  • プログラムにブートストラップに関する情報を与える
  • ユーザーが使うデバイスとプログラム名を入力して指示

bootは常にメモリ上の予め決められた場所にプログラムをロードする

  • 決められた場所にロードされる
  • 別の場所に自分自身のイメージをコピー(再配置)
  • bootプログラムはコピーされたあとのメモリ位置から実行を開始するように作成

f:id:shigemk2:20150523170717p:plain

14.3 カーネルの初期化

  • bootプログラムによって初期化されたFreeBSDカーネルはアプリケーションプログラムを実行する準備を整えるために初期化作業を行う
  • 初期化の手続き三段階
  • アセンブリ言語による起動
  • カーネルモジュールをロードし初期化
  • ユーザレベルの始動スクリプトを実行

14.3.1 アセンブリ言語による起動

  • 機種に大きく依存する

  • 実行時スタックの準備

  • CPUの特定
  • 物理メモリ量の認識
  • 仮想アドレス変換ハードウェアの有効化
  • メモリ管理ハードウェアの初期化
  • SMPの運用に必要なテーブルの準備
  • 0番プロセスのためのハードウェアコンテキストの構築
  • C言語で記述されたシステムのエントリポイントの実行

  • 計算機状態の設定

  • すべての割り込みブロック

  • ハードウェアアドレス変換機能を無効化してメモリ参照が物理メモリアドレスで行われる

  • bootプログラムはブートデバイスの情報と一連のブートフラグをカーネルに渡す(これ以外の情報は持たない)

カーネルは物理メモリ上の決められた場所にロード

  • 物理メモリの最低位アドレスであることがおおい
  • アドレス変換ハードウェア有効化
  • 最高位に近いアドレスで始まる仮想メモリにマップ
  • すべての絶対アドレスを仮想メモリ番地から物理メモリ番地に変換

起動コードが行う2番めの作業 CPUの種類の特定

  • 古いバージョンのCPUに対応できるように未実装のハードウェア命令をソフトウェアでエミュレートする
  • 起動コードはCPUや仮想メモリサブシステムを初期化するのに機種依存のコードを実行することもある

メモ: GRUB

  • GRUBはbootに該当する
  • bootはカーネルをロードする。bootはファイルシステムを解釈している
  • UEFIはそれ用のアプリケーションがあって最低限のことができるライブラリが用意されている

  • MBRにGRUBのコードを読ませて実行させる(512bに収まるようなものではない)

  • GRUBからvmlinuz(カーネルをgzip圧縮しているやつ)を読み込んで実行する

  • マルチブートプロトコル

  • GRUBからFreeBSDを起動したいときはPBRからbootの位置を特定してFreeBSDのカーネルを起動するというひと手間が必要。
  • なお、WindowsもFreeBSDのbootプログラムに相当するものがあって、仕組みは一緒。
  • F8はPBRからカーネルを読む段階でカーネルを読まずにいろいろ設定できる
  • F11はBIOSが起動した段階でどのMBRを使うかを選ぶ

14.4 カーネルモジュールの初期化

  • 初期のBSDにおいてはハードウェアの初期化は完全に専用に書かれたコードによって行われていた。
  • FreeBSDではすべてのカーネルサービスをモジュール化して新しい機能を追加できる

カーネルモジュール2種類

  • カーネルローダブルモジュール(実行時にシステムロードしてアンロード)
  • 永続的カーネルモジュール(ブート時にロードして実行中に削除できない)

  • すべてのモジュールは、2段階の階層に構造化(起動中にふたつの段階がある)

f:id:shigemk2:20150523173151p:plain

  • 起動システム→サブシステム→関連モジュール という順番 系統的というのは枝分かれしている意味合い。

アセンブリ言語コードによる処理が完了すると

  • Cで記述された最初の関数 mi_startup()関数を実行する
  • モジュールのリストをソートしてそれぞれについてfunction関数(SYSINITで登録したやつ)を実行
  • 機種依存のものと機種非依存のものを分離して、初期化作業する
  • プラットフォームに対する設定手続きを記述する作業は容易になっている

14.4.1 基本サービス

  • カーネルのサブシステムが使用する基本サービスを立ち上げないといけない

  • 以下が基本サービス。

f:id:shigemk2:20150523173908p:plain

  • 初期化の処理を分離している。
  • カーネルサービスはお互いに強く依存しているので順序変更は注意

ロックマネージャの初期化が完了すると

  • システム仮想記憶システムを有効にするためにvm_mem_init()を実行する
  • 仮想記憶システムの稼働
  • 内部アロケータのスタート
  • 記憶システムの資源を設定
  • メモリ確保

ユニプロセッサカーネルをSMP環境に移行するために

  • マルチカーネルは難しい
  • 異常終了したカーネルのデバッグも難しい
  • すべてのロックの取得と解放を監視するカーネルライブラリをFreeBSDが提供している

サービスによっては基本サービスの一部としてロックの割り当てが必要

  • SI_SUB_LOCKモジュールで早い段階でロックの初期化関数を実行しなければならないサービスを扱う
  • これはカーネルの中のサービスなのではなかろうか
  • ロック自体を扱うのがSUB_LOCK

イベントハンドラモジュール

  • イベント発生時にカーネルから実行される関数を登録のために使われる
  • SI_SUB_EVENTHANDLERは他のモジュールがスタート時に使用できるようにする基本サービスの一部

最後にスタートされる基本サービスはカーネルモジュールローダ

  • module_init()関数でシステム終了時にすべてのモジュールをアンロードするためのイベントハンドラを登録

  • ここまでが基本サービスの実行の準備

基本サービスの実行が始まるとカーネルのCPUに関する準備作業は終了。

  • 次はハードウェア的なところをやっている
  • APIC(Advanced Programmable Interrupt Controller) 割り込みサポートとか他のCPUの存在確認とか
  • ここまでやったらCPUそのものを起動。
  • 残された初期化作業をやる
  • startrtclock()関数で実時間クロックをスタート
  • バッファシステムの起動

  • FreeBSDは安定記憶に強く依存しているので、早期にスワッピングとかメモリのページングとかの資源にアクセスできるようにバッファシステムはブートシステムの初期段階で設定する。

SMPシステムはAPICやCPUモジュールよりもあとにスタートされる

  • cpu_mpモジュールのSYSINITのorder引数はSI_ORDER_SECOND
  • APICはSMPシステムに絶対不可欠
  • mp_start()を実行することでSMPシステム上のすべてのプロセッサが実行可能な状態になる

起動周りでコメント

  • 依存関係がいろいろあることくらいわかるかなっていう
  • マルチCPUのことを考えると面倒かも

  • APICモジュール→CPUモジュール→SMPモジュールの順番でモジュールが起動される

  • CPUモジュールによって2番め以降のコアが使えるようになる。

P638まで。

次回、最終回。

書籍など

BSDカーネルの設計と実装―FreeBSD詳解

BSDカーネルの設計と実装―FreeBSD詳解

  • 作者: マーシャル・カークマキュージック,ジョージ・V.ネヴィル‐ニール,砂原秀樹,Marshall Kirk McKusick,George V. Neville‐Neil,歌代和正
  • 出版社/メーカー: アスキー
  • 発売日: 2005/10/18
  • メディア: 単行本
  • クリック: 122回
  • この商品を含むブログ (57件) を見る

Rewrite 初回限定版

Rewrite 初回限定版