apt-get upgrade gcc considered harmful

いまの仕事に就いたことで触れることのできた興味深いソフトウェア開発手法のひとつに,成果物に関するものであれば何であれ"ソースコード"のバージョン管理システムに関連づけるというものがある.使用するライブラリは言うに及ばず,ビルドに使用するコンパイラのバージョンまでもを,ソースコードと同列にバージョン管理するわけだ.
このような環境で仕事を続けることしばし,ふと気付けば,多くの Linux ディストリビューションで喧伝されるうたい文句,「依存関係を賢く管理し,多数のユーザーによってテストされ,コマンド一発でインストールもアップデートもできる多数の開発ツール!」,というものは酷く色あせて聞こえるようになっていた.
むしろ逆だ.ソースコードバージョン管理システムとは異なるコマンドによってコンパイラやライブラリのバージョンが変化する!? そんなものは悪い冗談にしか聞こえない.そこにはコミットログも存在しなければ,コミット前のスモークテストも存在しない.ブランチを切ることもできなければ,不具合を見つけてもソースコードのようにはロールバックすることもできない.


などというようなことを『継続的デリバリー』を読みつつうなずいていた年末.いやー,本に書いてあるようなお手本運用を間近で見つつ仕事できるってのは刺激になっていいですねー.それではみなさま,来年も良いビルドを.
継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化

サービスを最小特権で実行する

今回は Windows サービスを作成する上でのセキュリティ上のポイントを軽く紹介する.
Windows Vista では,サービスをより安全に実行するために Service Control Manager (SCM) の改善が行われている.ポイントとなるのは,必要特権リストの指定が可能になったこと,および制限された SID を割り当てられるようになったことだ.
たとえば,特定ファイルを物理メモリ上に保持し続けるサービスを作りたいとする.この処理をサービスにする必要があるのは,それが特権を必要とするからだ.VirtualLock API でロック可能なメモリ領域は通常 30 ページに制限されており,SetProcessWorkingSetSize API でその制限を拡大するには,SE_INC_BASE_PRIORITY_NAME 特権が必要である.しかし,単純に System アカウントで動くサービスを作ったのでは,余分な特権まで有効にされてしまう.これは最小特権の原則に反する.
ここで,以下のように ChangeServiceConfig2 API を利用することで,サービスに付与される特権を制限できる.

SERVICE_REQUIRED_PRIVILEGES_INFO privileges_info = {};
privileges_info.pmszRequiredPrivileges = SE_INC_BASE_PRIORITY_NAME _T("\0");
ChangeServiceConfig2(service_handle, SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO,
                     &privileges_info);

また,このサービスはファイルやレジストリに書き込む必要がない.そこで,以下のように設定することで,意図せずファイルやレジストリに書き込もうとしたときに失敗するように構成できる.

SERVICE_SID_INFO sid_info = {};
sid_info.dwServiceSidType = SERVICE_SID_TYPE_RESTRICTED;
ChangeServiceConfig2(service_handle, SERVICE_CONFIG_SERVICE_SID_INFO,
                     &sid_info);

このように設定したサービスを起動し,Process Explorer でセキュリティ情報を見てみる.
f:id:NyaRuRu:20121015010639p:plain
Restricted SID が設定されていること,"NT AUTHORITY\WRITE RESTRICTED" SID が設定されていること,特権が大幅に削除されていることなどがわかる.
実際,OS 標準のサービスは概ね最小特権で実行されている一方で,(残念ながら Microsoft Office を含む) OS 非標準のサービスは,デフォルトの特権を持ったまま動作しているものがほとんどだ.例えば,Office 2010 に付属する ImeDictUpdateService (Microsoft IME Dictionary Update) のセキュリティ属性は以下のようになる.
f:id:NyaRuRu:20121015010658p:plain
デフォルトで付与される全ての特権を持ったまま実行されていることが分かる.これが本当に IME の辞書アップデートサービスであるなら,SeDebugPrivilege (スーパー特権のひとつ.プロセスのセキュリティ設定を無視して,(保護されたプロセスをのぞく) すべてのプロセスを開くことができる.) や,SeTimeZonePrivilege (文字通りタイムゾーンを設定するための特権) をはじめとした,多くの特権を削除しても恐らく動作は可能だろう.

AppContainer 導入による Windows 開発への影響

Windows 8 では,AppContainer と呼ばれる新たな Sandbox メカニズムが導入される.Windows ストアアプリ (旧名: Windows Metro アプリ) や,Immersive モードの Internet Explorerレンダリングプロセスなどが AppContainer で動作するプロセスの代表格だ.Windows ストアアプリ制作者にとって AppContainer が重要になるのはもちろんのこと,Windows ストアアプリ内で動作する必要のある IMEアクセシビリティ系ツールの制作者も,AppContainer について理解する必要がでてくる.

AppContainer プロセスを確認する

Process Explorer 15.23 を用いて,AppContainer プロセスの特徴を確認してみよう.以下は Windows 8 付属の「ミュージック」アプリを起動し,Process Explorer にて表示してみたところだ.
まず目に付く点として,WWAHost.exe プロセスの Integrity 欄に AppContainer と表示されていることが挙げられる.
f:id:NyaRuRu:20121008185343p:plain
次に WWAHost.exe プロセスのプロパティを表示し,Security Tab を選択してみる.プロセスのグループ一覧に,AppContainer と Capability という見慣れないグループがある.
f:id:NyaRuRu:20121008185402p:plain
前者は Windows ストアアプリごとに割り当てられるユニークな SID である.Android 環境でアプリケーションごとに User ID が割り当てられるのと似ている.後者の Capability は,この Windows ストアアプリに許可された動作と対応している.「ミュージック」アプリは,

  • インターネット接続へのアクセス
  • ミュージックライブラリへのアクセス

が許可されているようだ.
このように,AppContainer は,従来のトークン/SIDによるアクセス制御の拡張として実装されていることが分かる.例としては,Windows ストアアプリごとに SID が付与されることから,ある Windows ストアアプリにだけ,特定ディレクトリ以下へのアクセスを許すようなポリシーを設定することも可能である.

あるプロセスが AppContainer に属しているかどうかをプログラム的に判定する

あるプロセスが AppContainer に属しているかどうかをプログラム的に判定するには,プロセスのトークンハンドルに対して GetTokenInformation API を使用する.Windows 8 (SDK) 以降では TokenIsAppContainer フラグが使用でき,ここで 0 が返ってこなければプロセスは AppContainer で動作している.

あるプロセスが Immersive Mode かどうかをプログラム的に判定する

あるプロセスが没入型 UI を使用しているかどうかの判定には IsImmersiveProcess API を用いる.この用途で GetTokenInformation/TokenIsAppContainer フラグを使用するべきではない.なぜか?
それは Windows ストアアプリ風アプリケーション全てが AppContainer 内で動作するわけではないからだ.ブラウザという大きな例外がある.
Metro スタイル対応デスクトップ ブラウザーの開発」というホワイトペーパーに解説されているとおり,Windows 8 対応のブラウザには以下の 3 種類が存在する.

  1. Windows Store app: AppContainer 内で動作する
  2. Desktop browsers: Windows 7 までと同様のモデルで動作する.Win32 API にフルアクセスが可能で,JIT コンパイルや様々なマルチプロセス技術が利用可能.
  3. New experience enabled desktop browser: Windows ストアアプリ風の没入型 UI を備えつつ,Win32 API にフルアクセスが可能で,JIT コンパイルや様々なマルチプロセス技術が利用可能.

この 3 つ目が問題だ.結果として,没入型 UI かどうかの判定と AppContainer で動いているかどうかの判定は分けて考える必要がある.

AppContainer の SID を取得する

プロセスのトークンハンドルに対して GetTokenInformation API を使用する.Windows 8 (SDK) 以降では TokenAppContainerSid フラグが使用でき,ここで返ってきたものが AppContainer の SID である.

AppContainer プロセスのオブジェクト名前空間

引き続き,Process Explorer で AppContainer の動作を調べてみる.「View」→「Lower Pane View」から「Handles」を選択し,プロセス内で開かれているハンドル情報を調べてみよう.
開かれているハンドル名を見ていくと,セッション名前空間にさらに AppContainer SID が接頭辞として付加されていることに気付く.
f:id:NyaRuRu:20121008185545p:plain

\Sessions\1\AppContainerNamedObjects\S-1-15-2-43664394-2685677502-394080391-3933305958-4167273977-1510959782-2102270723\RPC Control\OLE36E12F064548B659D96055B91BF5

これに対応すると考えられる GetAppContainerNamedObjectPath API の Remarks が興味深い.この Remarks は,アクセシビリティ系など,別プロセス内で動作するツールに向けて書かれたものだ.

For assistive technology tools that work across Windows Store apps and desktop applications and have features that get loaded in the context of Windows Store apps, at times it may be necessary for the in-context feature to synchronize with the tool. Typically such synchronization is accomplished by establishing a named object in the user's session. Windows Store apps pose a challenge for this mechanism because, by default, named objects in the user's or global session are not accessible to Windows Store apps. We recommend that you update assistive technology tools to use UI Automation APIs or Magnification APIs to avoid such pitfalls. In the interim, it may be necessary to continue using named objects.

GetAppContainerNamedObjectPath

デフォルトでは Windows Store アプリからは従来の名前付きオブジェクトにアクセスできないものの,アクセス権を設定しさえすればデスクトップアプリと Windows Store アプリの間で名前付きオブジェクトを共有することが依然として可能であることが示唆されている.なお,これは暫定的な措置であり,本来は UI Automation API や拡大鏡 API を用いて対処して欲しいようだ.

All Application Packages グループを利用したアクセス制御

実は,Windows 8 では ALL APPLICATION PACKAGES と呼ばれる特殊なグループが追加されている.このグループへのアクセス許可を設定することで,Windows Store アプリに一括して動作の許可や拒否を設定することができる.
これについては,ファイルシステムの例がわかりやすい.%SystemRoot% や %ProgramFiles% のセキュリティ設定を見てみよう.Windows 8 では,デフォルトで ALL APPLICATION PACKAGES グループに対する「読み取りと実行」「フォルダーの内容の一覧表示」「読み取り」アクセスが許可されている.
f:id:NyaRuRu:20121008185631p:plain
仮に IME を開発するのであれば,仮に AppContainer 内で動作する場合であっても,ACL 的にはこれらのディレクトリ (以下) に配置された設定ファイルや辞書データは自由に読み取りできるものと考えられる.
また,試したわけではないが,AppContainer 内で動作する Windows Store アプリであっても,%ProgramFiles% 以下にインストールされたアプリケーション一覧や置かれたファイルの内容を取得することぐらいは可能なのかもしれない.これらの領域にセキュリティやプライバシーに関わるデータが置かれる場合は,悪意のある Windows Store アプリに読み取られる恐れがないか改めて確認されたい.
さて,ALL APPLICATION PACKAGES だが,ファイルオブジェクト以外にも当然利用されているようだ.
以下は,TSF が利用していると思われる Mutex オブジェクトのセキュリティ情報である.
f:id:NyaRuRu:20121008185714p:plain
ALL APPLICATION PACKAGES に同期とクエリ許可が与えられていることが分かる.このように,DACL を設定することで,Windows Store アプリとデスクトップアプリの間で名前付きオブジェクトを共有することが可能になるのだろう.
Windows Store アプリとデスクトップアプリの間のプロセス間通信についてもいずれいくつか書いてみたい.

参考

Google Chrome をデバッグする (1)

Google Chrome のデイリーリリースバージョンは,炭鉱にて危険をいち早く察知するカナリアにたとえられ Canary 版と呼ばれている.
(炭鉱で戦うものたちの熱い物語については『炭鉱の庭師』を参照されたい)
Google Chrome Canary 版は以下のページからインストールできる.
https://tools.google.com/dlpage/chromesxs
Canary 版と安定版は,インストール先フォルダから使用するプロファイルまで異なる別アプリケーションである (Side-by-side インストールと呼ばれている).両者を同時に起動することももちろん可能だ.また,Canary 版であっても Google Update の対象になることから,一度インストールしてしまえば後は自動で trunk を追いかけてくれる.
実は,Canary 版にはもうひとつ大きな特徴がある.それは,ビルドの副産物であるデバッグシンボルが公開されていることだ.ここで,デバッグシンボルが公開されていることで何が可能になるかについて,数回にわたって紹介してみたい.

シンボルサーバの登録

Windows エコシステムでのシンボルサーバの登録には,環境変数 _NT_SYMBOL_PATH を使用するのが一般的だ.Microsoft のシンボルサーバと Chrome のシンボルサーバを登録する場合,_NT_SYMBOL_PATH は次のように設定することになる.

_NT_SYMBOL_PATH=srv*c:\SymCache*http://msdl.microsoft.com/download/symbols;srv*C:\SymCache*http://chromium-browser-symsrv.commondatastorage.googleapis.com

"C:\SymCache" は別の名前に変更することも可能である.このフォルダにダウンロードされたシンボルデータがキャッシュされるので,なるべく高速なドライブを設定すると良い.
次に,よく利用する Sysinternals のツール群についてもシンボルパスを設定する.
f:id:NyaRuRu:20120930014804p:plain
Process Explorer と Process Monitor それぞれで,Options から Configure Symbols を選択する.Dbghelp.dll path については,Windows SDK 付属のものを選ぶとよい.64-bit 環境では x64 版の Dbghelp.dll を選択しよう.Symbol paths については先ほど同様に以下を設定する.

srv*c:\SymCache*http://msdl.microsoft.com/download/symbols;srv*C:\SymCache*http://chromium-browser-symsrv.commondatastorage.googleapis.com

Process Explorer を利用したハングアップ解析

Google Chrome Canary 版が無反応になったなら,Process Explorer を起動してみよう.デバッグシンボルが完備されていればその場でスレッドのコールスタックを見るのも容易である.(ただし,ここで最も慎重な次の一手はプロセスダンプをとることだ)
状況の保全のため,プロセスの全スレッドを中断させてみよう.一般的に,Chrome のウィンドウが無反応になったときには Chrome のブラウザプロセスの問題である.Process Explorer の "Process" 欄を何度かクリックして,表示モードをプロセスツリーに変更しよう.ここでルートに当たる chrome プロセスがブラウザプロセスである.(別の識別法として,プロセスの起動引数に --type オプションが付いていないプロセスを選ぶという方法もある)
f:id:NyaRuRu:20120930014912p:plain
ブラウザプロセスを見つけたら,コンテキストメニューから Suspend を選択する.これでブラウザプロセスの動作が中断する.動作を中断させたら,コンテキストメニューから Properties を選択し,Threads タブに移動する.
f:id:NyaRuRu:20120930014932p:plain
ここでスレッドを選択し Stack ボタンをクリックすると,該当スレッドのコールスタックが表示される.
f:id:NyaRuRu:20120930014952p:plain
コールスタックを表示すると,非同期でシンボルのダウンロードが始まる.しばらくすると,Chrome のどういった関数が呼び出されていたのかが表示されるだろう.UI スレッドを探し出し,そのコールスタックを探し当てたら,それがハングアップの現場である.

Process Monitor を利用した I/O モニタリング

Process Monitor を利用することで,以下のような動作のモニタリングが可能になる.

  • 任意のファイルアクセス
  • 任意のレジストリアクセス
  • 任意のプロセス起動/スレッドイベント/DLL のロードとアンロード
  • 任意のネットワークアクセス

シンボルがセットされていると,これらの各イベント発生時のコールスタックが取得できるようになる.また,Tools メニューの各 Summary 項目から,選択したイベントの集計やクロス集計も可能である.例えば,プロセス起動時から終了時までの間にあるファイルへ行われた全てのファイルアクセスについて,コールスタックごとにその割合を分析するといったことが可能だ.

f:id:NyaRuRu:20120930015009p:plain
Chrome を起動してあるページをブラウズし,終了するまでの間に行われた約 2000 回のファイル読み取りアクセスを,コールスタックを用いてブレイクダウンしている図

Process Monitor を用いた解析テクニックについては,例えば次の記事などが参考になる.

次回,API モニタ編に続く.

SSD なら動作を変えるアプリケーションを作る

Windows 7 以降の OS では,SSD 上のボリュームに対してデフラグがスケジューリングされません.これと同様に,ドライブ特性に応じた動作戦略の変更をアプリケーションでも行いたいこともあるでしょう.そのような場合にアプリケーションに組み込めるように,簡単なサンプルコードを書いてみます.
元ネタは,
Windows 7 Disk Defragmenter User Interface Overview - The Storage Team at Microsoft - File Cabinet Blog - Site Home - TechNet Blogs
で解説されているアルゴリズムです.

以下,物理ドライブ "\\.\PhysicalDrive0" について,"no seek penalty" かどうかと,"nominal media rotation rate" かどうかの取得を行います.なお,前者はディスクポートドライバの動作に依存するため,Windows 7 以降でなければ使用できませんが,管理者権限なしで動作するというメリットがあります.後者は ATA8-ACS に対応したデバイスであれば,Windows 7 より前の OS でも動作しますが,動作には管理者権限が必要です.
上記記事に解説されているように,これらの関数を組み合わせて使ってもよいですし,どちらか一方のみを使いある程度の false negative は諦めるという手もあります.

#include <Windows.h>
#include <WinIoCtl.h>
#include <Ntddscsi.h>
#include <Setupapi.h>

#include <iostream>
#include <string>

using std::wstring;
using std::wcout;
using std::endl;

// Returns S_OK if |physical_drive_path| has no seek penalty.
// Returns S_FALSE otherwise.
// Returns E_FAIL if fails to retrieve the status.
// |physical_drive_path| should be something like
// "\\\\.\\PhysicalDrive0".
HRESULT HasNoSeekPenalty(const wstring& physical_drive_path) {
    // We do not need write permission.
  const HANDLE handle = ::CreateFileW(
      physical_drive_path.c_str(), FILE_READ_ATTRIBUTES, 
      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (handle == INVALID_HANDLE_VALUE) {
    return E_FAIL;
  }

  STORAGE_PROPERTY_QUERY query_seek_penalty = {
    StorageDeviceSeekPenaltyProperty,  // PropertyId
    PropertyStandardQuery,             // QueryType,
  };
  DEVICE_SEEK_PENALTY_DESCRIPTOR query_seek_penalty_desc = {};
  DWORD returned_query_seek_penalty_size = 0;
  const BOOL query_seek_penalty_result = DeviceIoControl(
    handle, IOCTL_STORAGE_QUERY_PROPERTY,
    &query_seek_penalty, sizeof(query_seek_penalty),
    &query_seek_penalty_desc,
    sizeof(query_seek_penalty_desc),
    &returned_query_seek_penalty_size, NULL);
  CloseHandle(handle);
  if (!query_seek_penalty_result) {
    // failed to retrieve data.
    return E_FAIL;
  }

  return !query_seek_penalty_desc.IncursSeekPenalty ?
      S_OK : S_FALSE;
}

// Returns S_OK if |physical_drive_path| has nominal media
// rotation rate in terms of ATA8-ACS specification.
// http://www.t13.org/Documents/UploadedDocuments/docs2007/D1699r4-ATA8-ACS.pdf#Page=179
// Returns S_FALSE otherwise.
// Returns E_FAIL if fails to retrieve the status.
// |physical_drive_path| should be something like
// "\\\\.\\PhysicalDrive0".
HRESULT HasNominalMediaRotationRate(
    const wstring& physical_drive_path) {
  // In order to use IOCTL_ATA_PASS_THROUGH,
  // We *do* need read/write permission, which means
  // that the caller has admin privilege.
  const HANDLE handle = CreateFileW(
    physical_drive_path.c_str(),
    GENERIC_READ | GENERIC_WRITE, 
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (handle == INVALID_HANDLE_VALUE) {
    return E_FAIL;
  }

  struct ATAIdentifyDeviceQuery {
    ATA_PASS_THROUGH_EX header;
    WORD data[256];
  };

  ATAIdentifyDeviceQuery id_query = {};
  id_query.header.Length = sizeof(id_query.header);
  id_query.header.AtaFlags = ATA_FLAGS_DATA_IN;
  id_query.header.DataTransferLength =
      sizeof(id_query.data);
  id_query.header.TimeOutValue = 3;  // sec
  id_query.header.DataBufferOffset =
      sizeof(id_query.header);
  id_query.header.CurrentTaskFile[6] =
      0xec;  // ATA IDENTIFY DEVICE command

  DWORD retval_size = 0;
  const BOOL result = DeviceIoControl( 
    handle, IOCTL_ATA_PASS_THROUGH,
    &id_query, id_query.header.DataTransferLength,
    &id_query, id_query.header.DataTransferLength,
    &retval_size, NULL);
  if (!result) {
    return E_FAIL;
  }
  const int kNominalMediaRotRateWordIndex = 217;
  // RPM == 1 means this is non-rotate device
  return id_query.data[kNominalMediaRotRateWordIndex] == 1 ?
      S_OK : S_FALSE;
}

int main() {
  const wstring kDrive = L"\\\\.\\PhysicalDrive0";

  switch (HasNoSeekPenalty(kDrive)) {
    case S_OK:
      wcout << kDrive << L" has no seek penalty." << endl;
      break;
    case S_FALSE:
      wcout << kDrive << L" has seek penalty." << endl;
      break;
    default:
      wcout << L"failed to retrieve the status." << endl;
      break;
  }
 
  switch (HasNominalMediaRotationRate(kDrive)) {
    case S_OK:
      wcout << kDrive << L" has no seek penalty." << endl;
      break;
    case S_FALSE:
      wcout << kDrive << L" has seek penalty." << endl;
      break;
    default:
      wcout << L"failed to retrieve the status." << endl;
      break;
  } 

  return 0;
}

さて,"C:\Windows\System32\calc.exe" のようなパス名から,そのファイルが存在する物理ドライブ名を取得するにはどうすればよいでしょうか?
これには,

  1. GetVolumePathName API を利用してパスからマウントポイントを取得する
  2. GetVolumeNameForVolumeMountPoint API を利用して,論理ボリューム名を取得する
  3. IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS IO Control を利用して,物理ドライブの番号を取得する

という手順を踏みます.後は,得られた番号を "\\.\PhysicalDrive" の末尾に付ければ,物理ボリューム名が判明します.
なお,ダイナミックボリュームでは,複数の物理ドライブにまたがった論理ボリュームが作成可能なことに注意してください.以下の関数は,指定されたパスに対応する物理ドライブ番号を返します.

vector<int> GetExtentsFromPath(const wstring& path) {
  vector<int> extents;

  wchar_t mount_point[1024];
  if (!GetVolumePathNameW(
        path.c_str(), mount_point, ARRAYSIZE(mount_point))) {
    return extents;
  }

  wchar_t volume_name[1024];
  if (!GetVolumeNameForVolumeMountPointW(
          mount_point, volume_name, ARRAYSIZE(volume_name))) {
    return extents;
  }

  wstring volume = volume_name;

  // remove trailing '\\'
  volume.resize(volume.size() - 1);

  // We do not need write permission (nor admin rights).
  const HANDLE volume_handle = CreateFileW(
      volume.c_str(), FILE_READ_ATTRIBUTES, 
      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL, NULL);

  VOLUME_DISK_EXTENTS initial_buffer = {};
  DWORD returned_size = 0;
  const BOOL get_volume_disk_result = DeviceIoControl(
      volume_handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
      NULL, 0, &initial_buffer, sizeof(initial_buffer),
      &returned_size, NULL);
  const DWORD query_size_error = GetLastError();
  if (get_volume_disk_result != FALSE &&
      initial_buffer.NumberOfDiskExtents == 1) {
    extents.push_back(initial_buffer.Extents[0].DiskNumber);
    return extents;
  }
  if (query_size_error != ERROR_MORE_DATA) {
    return extents;
  }

  const size_t buffer_size =
      sizeof(initial_buffer.NumberOfDiskExtents) + 
      sizeof(initial_buffer.Extents) *
      initial_buffer.NumberOfDiskExtents;
  char* underlaying_buffer = new char[buffer_size];
  VOLUME_DISK_EXTENTS* query_buffer =
      reinterpret_cast<VOLUME_DISK_EXTENTS *>(
          &underlaying_buffer[0]);
  const BOOL devide_ioc_result = DeviceIoControl(
      volume_handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
      NULL, 0, 
      query_buffer, buffer_size, &returned_size, NULL);
  const DWORD device_detail_result_error = ::GetLastError();
  if (!!devide_ioc_result) {
    for (DWORD i = 0;
         i < query_buffer->NumberOfDiskExtents; ++i) {
      extents.push_back(query_buffer->Extents[i].DiskNumber);
    }
  }
  delete[] underlaying_buffer;
  CloseHandle(volume_handle);
  return extents;
}

幸いなことに,上記処理は管理者権限なしでも動作します.Windows 7 以降では,管理者に昇格する必要なしに,SSD 上で動作しているらしいかどうかをアプリケーションが判別できるということになります.

Metro スタイルアプリケーションと IME

Metro では,AppContainer という特殊な環境でアプリケーションが実行されます.IME DLL は *1,対象の Metro スタイルアプリケーションプロセスに読み込まれ,AppContainer の管理下で動作することが求められます.

実際,Microsoft は Windows 8 Release Preview の公開に合わせ,Guidelines and checklist for IME development (Metro style apps) というガイドラインの提供を開始しました*2.同ドキュメントには,AppContainer 内で IME の機能を実装する上で,次のようなケースに注意せよとあります.

  • 辞書ファイルの置き場所
  • インターネットを利用したアップデート
  • 学習機能
  • プロセス間での(設定や学習)情報の共有

同ドキュメントには,IME の学習機能や,プロセス間での(設定や学習)情報の共有機能の実装方法として,その AppContainer で可能であればウェブサービスを経由してこれらの機能を実装するよう書かれています.Mac や Android のように IME プロセスを分離してくれていれば,IME 単体に別のアクセスコントロールを適用することも可能だったのでしょうが……

なお,Metro アプリケーションとデスクトップアプリケーションでは,利用可能なテクノロジに違いがあります.詳細については API reference for Metro style apps から辿ることができます.Metro 対応 IME では IMM32 ではなく TSF を使う必要があることはガイドラインで明記されていましたが,その他の API についてははっきりとは書かれていません.公開されると言われている Metro 対応 IME サンプルでも読まないとはっきりしない予感がします.
なお,こうして作られた IME DLL は,従来のデスクトップアプリケーションに読み込まれても動作する必要があります.まさに Run Anywhere,Universal Binary というわけです.

*1:Metro スタイルアプリケーションは,TSF のみのサポートとなりますから,IME というより Text Input Processor; TIP DLL と呼ぶ方が適切かもしれませんが

*2:一方で,予定されていた Metro アプリ用 IME サンプルに関する情報は[http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/6abc8b91-110c-4d60-b5b7-e113144902d9:title=削除されました].

Chromium Sandbox を用いた保護モード

Chromium Sandbox は,乱暴に言えば

  1. 対象プロセスの権限や動作をとにかく制限する
  2. それだと目的の動作もできなくなることがあるので,本当に必要な処理についてはより強い権限を持ったプロセスで代理処理する

という二段構成になっています.

2. の代理処理は,Sandbox 化されたプロセス内での API 呼び出しをフックして行います*1.悪意のあるコードが API フックを回避し,本来のシステムコールに到達した場合,プロセス本来のアクセス権限によるチェックが行われます.このケースまで想定して,前者の「とにかく」のレベルが決まります.

1. の制限は強ければ強いほど良いため,Chromium Sandbox では,先ほど出てきた整合性レベルに加え,プロセスのアクセストークンからの権限削除,Job を利用した制約,デスクトップ分離などさまざまなテクニックが使われています.実際には,アプリケーション本来の目的を阻害するような制約は諦めざるをえないため,アプリケーションごとにどの制約を組み合わせるかを選択します.Adobe Reader のケースでは,一部の設定について PC 管理者がカスタマイズすることを許可しています.

Adobe Reader に関しては,次の文献などで詳細な解析が行われています.
http://media.blackhat.com/bh-us-11/Sabanal/BH_US_11_SabanalYason_Readerx_WP.pdf

Chromium Sandbox のような特殊な環境で IME が動作しているとき,正常に動作しているかどうかを判断するのは難しいものがあります.多くの場合,プロセスを起動するような操作しばしば失敗します.これは,辞書ツールや文字パレットが別プロセスとして実装されているケースでは明らかになるでしょうが,これらのツールが対象プロセス内に表示されるウィンドウとして実装されている場合には表面化しないかもしれません.IME の確定履歴がディスク上に書き込めないというケースもあります.Process Monitor でのログを見るだけで問題が明らかになることもありますが,いずれにせよ IME についての理解は必要です.

*1:AdHoc なルールを透過的に適応するという点では Windows の互換性スイッチに似ているとも言えます