他人の空似

2014 年 6 月 8 日

PrivateNamespaceを使ったAppContainerとのプロセス間通信

Filed under: 未分類 — 中の人 @ 4:17 AM

以前の記事でAppContainerの内外で1対1プロセス間通信する方法を紹介しました。
しかし、指定したAppContainerに紐づく名前空間を使用する関係上、不特定多数のAppContainerアプリからプロセス間通信を受ける場合などには使用できませんでした。
またそれ以外にも、AppContainerのプロファイルを作った後でなくてはいけなかったり、AppContainerのプロファイル名を知らなければならなかったりと通常のプロセス間通信よりも制限が多いという難点もありました。
そこで今回はPrivateNamespaceを使用して、これらの問題を解決してみようと思います。

いつものようにサンプルコード
http://wordpress.click3.org/garakuta/TestPrivateSharedMemory.zip

PrivateNamespace空間とは

Windowsのハンドルはそれぞれ所属する名前空間があります。
これらは通常WinObjなどを使えば見ることが可能ですが、実は例外としてPrivateNamespaceというものが存在します。

PrivateNamespaceは例外的に誰でも(AppContainerも含む)アクセスできる場所に配置されており、通常の名前空間のように相対パスや絶対パスなどを気にする必要がありません。
そのため、AppContainerへのアクセスを許可しただけではAppContainer側からそこへたどり着く方法がないという従来のハンドル制限を無視することができます。
とはいえ個々のPrivateNamespaceにはアクセスコントロールが効いているので、AppContainerから利用する場合はWinBuiltinAnyPackageSidなどを許可する必要はあります。

実際にやってみよう

サンプルコードのExampleApp.exeをTestPrivateSharedMemory.exeにD&Dすると黒窓が二つ上がり、ExampleAppのほうに数字を打ち込むともう片方にも反映されます。
その状態でさらにExampleApp.exeをダブルクリックして起動し、そちらに数字を打ってもちゃんと反映されます。
D&Dで起動したものはAppContainerで、通常起動は普通のデスクトップアプリとして動作しています。

次は具体的な実装方法を説明します。

コード説明

具体的な動作としては、PrivateNamespaceを作成もしくはオープンし、共有メモリーを開く際にそのPrivateNamespaceをオブジェクト名の前につけて作るだけです。
では実際のコードを見ていきましょう。

まずはPrivateNamespaceの作成から

bool CreatePrivateNamespace(SHARED_HANDLE &privateNamespace, const boost::shared_ptr<SECURITY_DESCRIPTOR> desc, const wchar_t * const boundaryName, const wchar_t * const name) {
  SECURITY_ATTRIBUTES attr;
  attr.nLength = sizeof(SECURITY_ATTRIBUTES);
  attr.bInheritHandle = FALSE;
  attr.lpSecurityDescriptor = desc.get();
  std::vector<SHARED_SID> sidList;
  sidList.push_back(GetWellKnownSid(WinBuiltinAnyPackageSid));
  HANDLE boundaryImpl = ::CreateBoundaryDescriptorW(boundaryName, 0);
  if (boundaryImpl == NULL) {
    return false;
  }
  BOOST_FOREACH(const SHARED_SID sid, sidList) {
    if (::AddSIDToBoundaryDescriptor(&boundaryImpl, sid.get()) == FALSE) {
      ::DeleteBoundaryDescriptor(boundaryImpl);
      return false;
    }
  }
  const SHARED_HANDLE boundary(boundaryImpl, &MyDeleteBoundaryDescriptor);
  privateNamespace.reset(::CreatePrivateNamespaceW(&attr, boundary.get(), name), &MyClosePrivateNamespace);
  if (!privateNamespace) {
    return false;
  }
  return true;
}

SECURITY_ATTRIBUTESとBoundaryDescriptorを構築、それをもとにCreatePrivateNamespaceWを呼び出しています。
nameが実際に共有メモリーなどを作成する際につける名前空間の名前で、boundaryNameは別プロセスから同じPrivateNamespaceを作る際に使用する識別子になります。
boundaryNameさえあっていれば同じ名前空間になるので、OpenPrivateNamespaceでnameを変えて実行すれば別名だが同じものとして動作するハンドルを取得できます。
また、AddSIDToBoundaryDescriptorでアクセスコントロールを行っており、WinBuiltinAnyPackageSidを指定することでAppContainerからのアクセスを許可しています。

次に呼び出し側

  SHARED_HANDLE privateNamespace;
  if (!CreatePrivateNamespace(privateNamespace, desc, L"boundaryName", L"privateNamespaceName")) {
    std::wcout << L"Error: PrivateNamespaceの生成に失敗しました" << std::endl;
    std::wcout << ::GetLastError() << std::endl;
    std::wcin.ignore();
    return 1;
  }
  const wchar_t * const sharedMemoryName = L"privateNamespaceName\\AppConteinerTestMemory";
  HANDLE handle;
  if (!CreateSharedMemory(handle, sharedMemoryName, sizeof(SharedStruct), desc)) {
    std::wcout << L"Error: 共有メモリーの生成に失敗しました" << std::endl;
    std::wcout << ::GetLastError() << std::endl;
    std::wcin.ignore();
    return 1;
  }

特に説明することはありません、「#{PrivateNamespaceの名前空間名}\#{オブジェクトの名前}」で共有メモリーを作成しているだけです。

その他AppContainerでのexeの起動などは以前解説したことがあるので省き、次はExampleAppのPrivateNamespaceのオープンから。

bool OpenPrivateNamespace(SHARED_HANDLE &privateNamespace, const wchar_t * const boundaryName, const wchar_t * const name) {
  std::vector<SHARED_SID> sidList;
  sidList.push_back(GetWellKnownSid(WinBuiltinAnyPackageSid));
  HANDLE boundaryImpl = ::CreateBoundaryDescriptorW(boundaryName, 0);
  if (boundaryImpl == NULL) {
    return false;
  }
  BOOST_FOREACH(const SHARED_SID sid, sidList) {
    if (::AddSIDToBoundaryDescriptor(&boundaryImpl, sid.get()) == FALSE) {
      ::DeleteBoundaryDescriptor(boundaryImpl);
      return false;
    }
  }
  const SHARED_HANDLE boundary(boundaryImpl, &MyDeleteBoundaryDescriptor);
  privateNamespace.reset(::OpenPrivateNamespaceW(boundary.get(), name), &MyClosePrivateNamespace);
  if (!privateNamespace) {
    return false;
  }
  return true;
}

OpenPrivateNamespaceWで先ほど別プロセスで作成したPrivateNamespaceをオープンしています。
この時boundaryの内容が作成側と異なると失敗するので注意が必要です。
また、前述のようにnameに関しては作成時と同一にする必要はありません。

次はこちらも呼び出し側

  SHARED_HANDLE privateNamespace;
  if (!OpenPrivateNamespace(privateNamespace, L"boundaryName", L"privateNamespaceName")) {
    std::wcout << L"Error: 名前空間のオープンに失敗しました" << std::endl;
    std::wcout << ::GetLastError() << std::endl;
    std::wcin.ignore();
    return 1;
  }
  void * ptr;
  const wchar_t * const sharedMemoryName = L"privateNamespaceName\\AppConteinerTestMemory";
  if (!CreateSharedMemory(ptr, sharedMemoryName, sizeof(SharedStruct))) {
    std::wcout << L"Error: 共有メモリーの生成に失敗しました" << std::endl;
    std::wcout << ::GetLastError() << std::endl;
    std::wcin.ignore();
    return 1;
  }

こちらも特に説明することはなく、オープンしたPrivateNamespaceで共有メモリーを作成しているだけです。

その他実際にプロセス間通信を行っているところなどはPrivateNamespaceとは関係がなく、ググればすぐにわかるので割愛します。

終わりに

これでAppContainerを含む複数のプロセスで1対多や多対多のプロセス間通信が行えるようになりました。
また今回は共有メモリーで行いましたが、Mutexやパイプなどその他のオブジェクトに関しても同様の手順で使用することができます。
あまり需要はないと思いますが、参考にしてくれる人がいれば幸いです。

ではまた。

2014 年 5 月 24 日

touhouSE ver1.15リリース

Filed under: 未分類 — 中の人 @ 10:12 PM

東方系dat展開ツールtouhouSE更新しました。
touhouSE

今回は弾幕アマノジャク対応。
とはいえ、新規フォーマット追加ではなくフォーマット誤認による展開失敗修正と、msgファイルの新規追加機能対応だけです。

2014 年 5 月 18 日

GetProcessMemory

Filed under: 未分類 — 中の人 @ 7:47 PM

指定プロセスを監視しメモリー使用量の推移を記録するツール。
http://wordpress.click3.org/garakuta/GetProcessMemory.zip

単に指定exeのメモリー使用量の推移をcsvに書きだすだけのツールです。

mrubyのバイトコードフォーマット解説その2

Filed under: 未分類 — 中の人 @ 12:57 AM

2014/05/14、mrubyでローカル変数情報を扱うようになりlocal_variablesメソッドも使用可能に。
それに伴いバイトコードのフォーマットも変更となり、新たにLVARセクションが追加されました。

そこで今回は追加されたLVARセクションについて、簡単にフォーマット紹介をしようと思います。

過去の記事の続きなので、まだの人はそちらからどうぞ。

いつものようにサンプルコード
http://wordpress.click3.org/garakuta/parse_mruby3.zip
内容は過去の記事の物をベースにローカル変数情報を追加してあります。
動作としては、parse_mruby.exeに.mrbファイルをD&Dするとローカル変数情報とirep情報を表示する、というものです。

この記事で対象としているmrubyは2014/05/17当時(git hash: 13db4da204d2bedec4c0c5de939e662a44d477a6 )の物です。
それ以降のmrubyを使用する場合は以下の解説の通りではない可能性があります。

セクション

ローカル変数は新設されたセクションに含まれています。
過去の記事でも書いていますがセクションには共通のセクションヘッダーと呼ぶべきものを含んでおり、それは以下の通りになります。

ubig8_t signature[4];
ubig32_t size;

LVARセクション

ローカル変数名を保持するセクションで、signatureは0x4C 0×56 0×41 0×52(ASCIIでLVAR)になります。
LVARセクション専用の追加ヘッダーは特に存在しません。

LVARセクションは一つのmrbファイル一つにつき一つのみ存在し、複数のメソッドが含まれている場合はそれらすべてのローカル変数情報を持ちます。
LVARセクションはセクションヘッダーを除くと二つの領域からなりローカル変数名を表すシンボルテーブルと、各レジスタとシンボルを紐づけるレコードテーブルに分けられます。

シンボルテーブル

ローカル変数名のシンボルを格納した領域。
含まれるirepすべてのローカル変数名情報のリストであり、重複も排除されている。
構造は以下の構造の通りになる。

ubig32_t count;
struct symbol {
  ubig16_t len;
  ubig8_t body[len];
} symbolList[count];

サンプル内で実際に読み込んでいるコードは以下の通り。

  static boost::shared_ptr<const LocalVariableSymbolList> Read(const uint8_t *ptr, unsigned int size) {
    boost::shared_ptr<const LocalVariableSymbolList> result;
    if (size < 4) {
      return result;
    }
    const unsigned int count = *reinterpret_cast<const endian::ubig32_t *>(ptr);
    ptr += 4;
    size -= 4;
    if (count * 1 > size) {
      return result;
    }
    std::vector<std::string> list(count);
    BOOST_FOREACH(std::string &str, list) {
      if (size < 2) {
        return result;
      }
      const unsigned int len = *reinterpret_cast<const endian::ubig16_t *>(ptr);
      ptr += 2;
      size -= 2;
      if (len > size) {
        return result;
      }
      str.assign(reinterpret_cast<const char *>(ptr), len);
      ptr += len;
      size -= len;
    }
    result.reset(new LocalVariableSymbolList(list));
    return result;
  }

最初にシンボルの個数を取得し、その個数分だけループをまわして文字列長を取得した後その長さだけ文字列として読み込み、最後にそれを保持するインスタンスを作成して返しているだけです。
irepのシンボルテーブルと違い終端文字列は含まない点に注意。
また現在の版だとブロック引数に(たとえ使用していなくても)常に空文字列がローカル変数名として割り当てられており、それがこのシンボルテーブルにも含まれていることがあります。

レコードテーブル

ローカル変数シンボルと実際にローカル変数が保持されているレジスタを紐づけるテーブル。
irepデータの数だけ存在し、深さ優先探索的にならんでいる。
構造は以下の通り

ubig16_t symbolIndex;
ubig16_t registerIndex;

symbolIndexは前述のシンボルテーブル内のインデックス、registerIndexはirep内の該当するローカル変数に紐づくレジスタのインデックスです。
サンプルで実際に読み上げている処理は以下の通り。

  static boost::shared_ptr<const LocalVariableData> Read(const uint8_t *ptr, unsigned int size, const boost::shared_ptr<const IrepRecord> irep, const boost::shared_ptr<const LocalVariableSymbolList> symbolList) {
    boost::shared_ptr<const LocalVariableData> result;
    if (static_cast<unsigned int>(2 + 2) * (irep->header->localCount - 1) > size) {
      return result;
    }
    std::vector<Record> records(irep->header->localCount - 1);
    BOOST_FOREACH(Record &record, records) {
      const unsigned int nameIndex = *reinterpret_cast<const endian::ubig16_t *>(ptr);
      if (nameIndex > symbolList->list.size()) {
        return result;
      }
      ptr += 2;
      size -= 2;
      record.name = &symbolList->list[nameIndex];
      record.registryIndex = *reinterpret_cast<const endian::ubig16_t *>(ptr);
      ptr += 2;
      size -= 2;
    }
    std::vector<boost::shared_ptr<const LocalVariableData> > childs(irep->child.size());
    for (unsigned int i = 0; i < childs.size(); i++) {
      const boost::shared_ptr<const LocalVariableData> child = Read(ptr, size, irep->child[i], symbolList);
      if (!child) {
        return result;
      }
      childs[i] = child;
      ptr += child->GetSize();
      size -= child->GetSize();
    }
    result.reset(new LocalVariableData(records, childs));
    return result;
  }

最初に該当するirepのローカル変数の数を取得している。
-1しているのは自信を指すselfの分は含まれないためだ。

次はローカル変数の数だけレコードを読み込み、シンボルとの関連付けを解決している。

その後子のirepに対して再帰実行して読み込んだのち、データを保持するインスタンスを作成して返している。

irepとの紐づけ

irepとの紐づけといってもレコードテーブルの時点で終わっているも同然です。
すでにirepごとに変数の紐付けは終わっており、あとはirepでの処理の際に該当するレコードからシンボルを引っ張ってくるだけです。

一応、サンプルにおけるローカル変数情報の表示部分を紹介します。

bool PrintLocalVariable(const boost::shared_ptr<const LocalVariableData> data, std::vector<unsigned int> &index) {
  std::cout << boost::format("lvar%s\n") % std::accumulate(index.begin(), index.end(), std::string(), IndexListJoin());
  BOOST_FOREACH(const LocalVariableData::Record &record, data->records) {
    std::cout << boost::format("reg[%d] : %s") % record.registryIndex % *record.name << std::endl;
  }
  std::wcout << L"\n";
  unsigned int i = 0;
  BOOST_FOREACH(const boost::shared_ptr<const LocalVariableData> &child, data->childs) {
    index.push_back(i);
    if (!PrintLocalVariable(child, index)) {
      return false;
    }
    index.pop_back();
    i++;
  }
  return true;
}

indexは階層表示用の配列でlvar->child[3]->child[2]->child[1]のirepのローカル変数情報を表示する際には[3, 2, 1]と保持しており、それを文字列に変換して使用しています。
あとは単にforeachで配列を展開して表示し、childがいれば再帰して呼び出しているだけです。

終わりに

いかがだったでしょうか。
新しくセクションが増えるというそれなりに大きな変更ですが、実はバージョン番号などには変化がありません。
Matzさん曰く「sectionが追加されただけで、かつ知らないsectionは読み飛ばす(はず)ですから、変更しなくても大丈夫だと思いました。」とのことで、過去の版で動作する限りは特にバージョンは上がらないようです。

今回の更新でローカル変数をevalで取得するようなコードも動くようになったので、また一歩rubyとの互換性が上がったと言えます。
この記事を読んで少しでもmrubyの動作について理解が深まれば幸いです。
では、また。

2014 年 5 月 12 日

AppContainer関連サンプル紹介

Filed under: 未分類 — 中の人 @ 2:47 AM

前々回はAppContainerでデスクトップアプリを起動する方法を説明しました。
今回はAppContainerでデスクトップアプリを作るに当たり問題になるプロセス間通信と、ファイルの読み書き権限の付与について紹介したいと思います。

いつものようにサンプルコード。
http://wordpress.click3.org/garakuta/AppContainerUtils.zip

ハンドル継承

Win32APIを調べているとハンドル継承という単語をよく見ます。
しかし、そういう機能もあるんだなーどまりで実際にどうやってハンドルを継承するのか知らない人も多いと思います。
そもそも通常のMedium権限で動くアプリの場合、ハンドルの継承なんかせずとも問題なくハンドルの生成が可能だからというのもあるでしょう。
そこで今回、自由にハンドルを作成できないAppContainer用にハンドル継承を試してみようと思います。

ハンドル継承とは

親プロセスで保持しているハンドルを子プロセスでも使用できるようにする機能です。
具体的には同じ数値を持つHANDLEで同じオブジェクトにアクセスできます。
これは親プロセスから降格したなどの理由により、子プロセスでは作れないが使用したいという用途で使われるものです。
AppContainerからは読み込み不能の領域に置かれた設定ファイルの読み込みなどが想定されます。

実際にやってみよう

サンプルコードのTestSharedMemory1を開いて、ExampleApp.exeをTestSharedMemory.exeにD&Dしてみてください。
黒窓が二つ上がったと思います。
そのうちExampleApp.exeのほうに数値を打ち込んでEnterを押すとTestSharedMemory.exeにも表示されると思います。
これは共有メモリーのハンドルを継承させてプロセス間通信しています。
exitと打ち込みEnterで終了できるので、適当に数値を入れて試してみるなどしてみて下さい。

次は具体的な実装方法を説明します。

コード説明

具体的にどうすればいいのかというと、ハンドル作成時と子プロセス作成時にハンドル継承フラグを有効にしたうえで、ハンドルを子プロセスに渡すだけです。
今回のサンプルでは試しに共有メモリーをハンドル継承で受け渡し、プロセス間通信を行っていきます。
では実際のコードも見ていきましょう。

まずは共有メモリーの作成。

bool CreateSharedMemory(HANDLE &handle, const wchar_t * const sharedMemoryName, const unsigned int sharedMemorySize) {
  // bInheritHandle = TRUEにすることでハンドルを継承可能にする
  SECURITY_ATTRIBUTES attr = {0};
  attr.nLength = sizeof(attr);
  attr.bInheritHandle = TRUE;
  attr.lpSecurityDescriptor = NULL;
  handle = ::CreateFileMappingW(INVALID_HANDLE_VALUE, &attr, PAGE_READWRITE, 0, sharedMemorySize, sharedMemoryName);
  if (handle == NULL) {
    return false;
  }
  return true;
}

重要なのは
attr.bInheritHandle = TRUE;
で、ここがTRUEだとこのハンドルを継承することが可能になります。

次は子プロセスの作成です。

bool RunImpl(SHARED_HANDLE &processHandle, const SHARED_SID sid, const boost::filesystem::path &path, const HANDLE arg) {
  ...
  // ハンドルをコマンドライン引数で渡す
  std::wostringstream woss;
  woss << path.wstring() << L" " << reinterpret_cast<unsigned int>(arg);
  wchar_t buf[1024];
  ::wcscpy_s(buf, woss.str().c_str());
  // bInheritHandles = TRUEでハンドルを継承させる
  if (::CreateProcessW(path.wstring().c_str(), buf, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, reinterpret_cast<LPSTARTUPINFO>(&startupInfoEx), &pi) == FALSE) {
    return false;
  }
  ...
}

CreateProcessWの第五引数のbInheritHandlesをTRUEにすることで、現在のプロセスで保持しているハンドル継承が有効なハンドルをすべて継承させます。
また、共有メモリーのハンドルをunsigned intにキャストしてコマンドライン引数で渡しています。
これは、ハンドル継承で使用可能になっても実際に使用できるハンドルを取得する方法がないためです。
実用する場合は後述の方法を用いてプロセス間通信を行ってハンドル本体を受け渡す方法がよいでしょう。

最後に子プロセス側でハンドルを実際に使用します。

bool CreateSharedMemory(void *&ptr, const HANDLE handle) {
  ptr = ::MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
  if (ptr == NULL) {
    return false;
  }
  return true;
}

int main(const unsigned int argc, const char * const * const argv) {
  ...
  std::istringstream iss(argv[1]);
  unsigned int handleImpl;
  iss >> handleImpl;
  const HANDLE handle = reinterpret_cast<HANDLE>(handleImpl);
  void * ptr;
  if (!CreateSharedMemory(ptr, handle)) {
    std::wcout << L"Error: 共有メモリーの生成に失敗しました" << std::endl;
    std::wcin.ignore();
    return 1;
  }
  SharedStruct &sharedStruct = *reinterpret_cast<SharedStruct *>(ptr);
  ...

ここは特に説明が必要な箇所はありません、普通にHANDLEから共有メモリーを取得しているだけです。

ハンドル継承まとめ

1:ハンドル継承有でハンドルを作成
2:ハンドル継承有で子プロセスを作成
3:何らかの手段でハンドルを受け渡し
4:通常通りハンドルを使用するだけ。

でハンドル継承は行えます。
かなり簡単ですが、三つ以上のプロセスで協調したり、別々に起動したプロセス間でやり取りするには向いていません。
やはり通常のプロセス間のように名前指定でプロセス間通信をしたいものです。
そこで次はAppContainerの内外間を名前指定でプロセス間通信をする方法を紹介します。

AppContainer内外間でプロセス間通信

AppContainerの中と外で同じ名前で共有メモリーを作った場合処理は成功しますが相手側に反映されません。
しかし同じAppContainerで同じ名前の共有メモリーを使うと正常にやり取りができます。
これはなぜかというと、AppContainer内で作成したハンドルは別の名前空間にリダイレクトされており、別物として処理されるからです。
つまり、外部側がそのリダイレクトされた先を指定すればやり取りができるわけです。

Winodwsのハンドル管理の仕組み

共有メモリーなど各ハンドルを取得するAPIを調べてみると、名前指定でハンドルを開けるものは少なくありません。
しかし、MSDNなどでは同じ名前だと関連付いたハンドルが返る、\は名前には使えない、とあるだけで具体的な説明がなく、名前空間などの説明がありません。
そこでWinObjを使ってみましょう。
これはオブジェクトマネージャーの名前空間を可視化するツール(要管理権限)で、現在存在している共有メモリーなどが属する名前空間を見ることができます。

WinObjで調べたところ、通常の名前付きオブジェクトは
\Sessions\#{セッション番号}\BaseNamedObjects\#{名前}

AppContainerから作成したものは
\Sessions\#{セッション番号}\AppContainerNamedObjects\#{AppContainerのSID}\#{名前}
に作られるようです。
また\Sessions\#{セッション番号}\BaseNamedObjects\AppContainerNamedObjectsに\Sessions\#{セッション番号}\AppContainerNamedObjectsへのシンボリックリンクが用意されていました。
これはつまり「AppContainerNamedObjects\#{AppContainerのSID}\#{名前}」を指定すればAppContainer内部で作ったオブジェクトとやり取りできそうです。

実際にやってみよう

サンプルコードのTestSharedMemory2を開いて、1と同じようにExampleApp.exeをTestSharedMemory.exeにD&Dしてみてください。
1と同じく数値を打ち込んでEnterでもう片方にも表示されると思います。
これは1とは違いそれぞれが共有メモリーをオープンしてプロセス間通信しています。

コード説明

2つ前の章でわざわざ各ハンドルの属する名前空間を調べましたが実際にこれらの知識は必要なく、いきなり直に名前空間を取得できるAPIがちゃんと用意されています。

具体的な呼び出し方は以下の通り

  ...
  const SHARED_SID sid = GetAppContainerSid(appContainerName);
  wchar_t buffer[4096];
  ULONG size;
  if (::GetAppContainerNamedObjectPath(NULL, sid.get(), _countof(buffer), buffer, &size) == FALSE) {
    std::wcout << L"Error: NameObjectPathの取得に失敗しました" << std::endl;
    std::wcin.ignore();
    return 1;
  }
  // 名前空間から取得する共有メモリーの名前を生成
  std::wostringstream woss;
  woss << buffer << L"\\" << sharedMemoryName;
  HANDLE handle;
  if (!CreateSharedMemory(handle, appContainerName, woss.str().c_str(), sizeof(SharedStruct))) {
    std::wcout << L"Error: 共有メモリーの生成に失敗しました" << std::endl;
    std::wcin.ignore();
    return 1;
  }
  ...

重要なのはGetAppContainerNamedObjectPathで、AppContainerのsidを渡すとそのAppContainerが所属するハンドルの名前空間を返します。
あとは#{名前空間}\#{名前}と文字列を構築して各APIに渡してやるだけでAppContainer内とやり取りができるというわけです。
ただし、AppContainerのプロファイルが存在していないとハンドルを取得できないので、Mediumで動く側はAppContainer側のプロセスを起動してから取得しに行くようにしましょう。

その他に関してはほぼ1と同じなので割愛します。

AppContainer内外間でプロセス間通信まとめ

1:GetAppContainerNamedObjectPathで名前空間を取得
2:AppContainerのプロセスを起動
3:名前空間を指定してハンドルを作成
4:あとは通常のプロセス間通信

という手順で問題なくプロセス間通信が行えることがわかりました。
通常はAppContainer内からでは開けないハンドルを継承可能にしたうえで、この方法で作った共有メモリー上に載せてプロセス間通信で渡し、といった運用になるかと思います。
またこの方法単独でプロセス間通信を行い、上位の権限が必要な処理は全部外部側のプロセスに用意してAPIを叩くように処理するというのもありでしょう。

さて、ここまででプロセス間通信で必要なものは大体説明で来たかと思います。
次はプロセス間通信から離れ、AppContainerの権限で読み書きできるファイルの作成についてです。

ファイルアクセスへのアクセス権限追加

AppContainer内部からは自由にファイルを読み書きできません。
その変わりにアプリケーション専用のフォルダが用意され、そこに設定ファイルを置いたりできるようになってはいますが、アプリによってはそれ以外にも読み書きできる場所が必要になることもあるでしょう。
そこで、ファイルやフォルダにAppContainerから読み書きできるようアクセス権を追加してみましょう。

アクセス権限の仕組み

アクセス権限はACL型であらわされ、これにAllowやDenyといった設定を追加していったものをSecurityDescriptorに入れて各APIに渡していく形となります。
AppContainerを対象としたい場合、CreateAppContainerProfileなどで取得したSIDを指定すればそのプロファイルにだけアクセス権限を出すことができます。
今回はAppContainer一つを対象とするのではなく、AppCOntainer全体に対するアクセス権限を付与してみましょう。

実際にやってみよう

AuthzAnyPackageFolderフォルダ内のAuthzAnyPackageFolder.exeに何かファイルかフォルダをD&Dしてみましょう。
その後対象のファイル/フォルダを右クリック=>プロパティ=>セキュリティをのぞいてみると「ALL APPLICATION PACKAGES」という項目が増えているはずです。
これはAppContainer全体を指しており、AppContainer下で実行されるどのアプリからもアクセスできるようになったことを表しています。

コード説明

まずはアクセス権限一覧であるACLを構築します。

typedef boost::shared_ptr<boost::remove_pointer<PACL>::type> SHARED_ACL;
unsigned int GetACLSize(const std::vector<SHARED_SID> &list) {
  unsigned int size = sizeof(ACL) + (sizeof(ACCESS_ALLOWED_ACE) * list.size());
  BOOST_FOREACH(const SHARED_SID &sid, list) {
    size += ::GetLengthSid(sid.get()) - sizeof(DWORD);
  }
  // align
  size = (size + sizeof(DWORD) - 1) & 0xfffffffc;
  return size;
}
SHARED_ACL CreatePACL(const std::vector<SHARED_SID> &list) {
  const unsigned int size = GetACLSize(list);
  const SHARED_ACL acl(reinterpret_cast<PACL>(new unsigned char[size]));
  if (::InitializeAcl(acl.get(), size, ACL_REVISION) == FALSE) {
    return SHARED_ACL();
  }
  BOOST_FOREACH(const SHARED_SID &sid, list) {
    if (::AddAccessAllowedAce(acl.get(), ACL_REVISION, GENERIC_ALL, sid.get()) == FALSE) {
      return SHARED_ACL();
    }
  }
  return acl;
}

特に難しいことはしておらず、InitializeAclでACLを初期化しAddAccessAllowedAceでSIDに許可を出しています。
ここでは使用していませんが、AddAccessDeniedAceでアクセス拒否を付加することもできます。
次はACLを用いてSECURITY_DESCRIPTORを構築します。

bool CreateSecurityDescriptor(boost::shared_ptr<SECURITY_DESCRIPTOR> &desc, SHARED_ACL &acl, const std::vector<SHARED_SID> &sidList, const SHARED_SID owner) {
  acl = CreatePACL(sidList);
  if (!acl) {
    return false;
  }
  desc.reset(new SECURITY_DESCRIPTOR());
  if (::InitializeSecurityDescriptor(desc.get(), SECURITY_DESCRIPTOR_REVISION) == FALSE) {
    return false;
  }
  if (::SetSecurityDescriptorDacl(desc.get(), TRUE, acl.get(), FALSE) == FALSE) {
    return false;
  }
  return true;
}

こちらも特に難しいことはしておらず、InitializeSecurityDescriptorで初期化してSetSecurityDescriptorDaclでSECURITY_DESCRIPTORにACLを設定しています。
ただし、SetSecurityDescriptor*のAPIはSECURITY_DESCRIPTOR内にコピーされるのではなく、あくまでポインターを保持するだけなため、設定したからと解放してしまうとSECURITY_DESCRIPTORの状態がおかしくなってしまうので注意が必要です。
今回の場合aclを開放してしまうと正常に処理されないため、使用側に受け渡して解放しないようにしています。

次に実際にファイル/フォルダに権限を設定するところです。

SHARED_SID GetWellKnownSid(const WELL_KNOWN_SID_TYPE type) {
  SHARED_SID sid(new unsigned char[SECURITY_MAX_SID_SIZE]);
  DWORD sidSize = SECURITY_MAX_SID_SIZE;
  if (::CreateWellKnownSid(type, NULL, sid.get(), &sidSize) == FALSE) {
    return SHARED_SID();
  }
  return sid;
}
bool AuthzAnyPackage(const char * const path) {
  const SHARED_SID owner = GetCurrentUserSid();
  boost::shared_ptr<SECURITY_DESCRIPTOR> desc;
  SHARED_ACL acl;
  std::vector<SHARED_SID> sidList;
  sidList.push_back(GetWellKnownSid(WinBuiltinAnyPackageSid));
  sidList.push_back(GetWellKnownSid(WinLocalSystemSid));
  sidList.push_back(owner);
  sidList.push_back(GetWellKnownSid(WinBuiltinAdministratorsSid));
  if (!CreateSecurityDescriptor(desc, acl, sidList, owner)) {
    return false;
  }
  if (::SetFileSecurityA(path, DACL_SECURITY_INFORMATION, desc.get()) == FALSE) {
    return false;
  }
  return true;
}

WinBuiltinAnyPackageSidというのが先ほどのサンプル実行でいう「ALL APPLICATION PACKAGES」に相当します。
あとは見たままですね、単純にSetFileSecurityのAPIにSECURITY_DESCRIPTORを渡しているだけになります。
またCreateFileに渡して最初からその権限でファイルを作成することなどもできます。

ファイルアクセスへのアクセス権限追加まとめ

1:ACLを構築
2:SECURITY_DESCRIPTORを構築
3:SetFileSecurityで権限付与

という手順でアクセス権限を追加できることがわかりました。
AppContainer内から触れるようにする必要がある時は事前にMediumの権限でアクセス権を付与しておくとよいでしょう。

終わりに

いかがだったでしょうか?
前々回と合わせて、これで大体のAppContainer内で動くデスクトップアプリは作れるようになったんじゃないかと思います。
実際に組むとすれば、Mediumで動くAPIサーバーのようなプロセスを用意し、そことプロセス間通信をしつつUIやコアの処理を行うAppContainerプロセス、とわけて動かすことでセキュリティリスクを抑えるという流れになるでしょう。

一人でもAppContainerに興味を持ってくれる人がいることを信じて。
では、また。

※また今回の記事を書くにあたって前回の記事にGetAppContainerNamedObjectPathを追加したので興味があれば見てほしい。

2014 年 5 月 8 日

AppContainer関連API情報まとめ

Filed under: 未分類 — 中の人 @ 2:54 AM

AppContainer関連のAPIで調べたことについてまとめておく。
これらの情報は公式ではなく、あくまで自分が個人的に調べたものであるので間違っていている可能性があることに注意。
また、実際に動くコードや解説などが目的の人はAppContainerでデスクトップアプリを起動してみたAppContainer関連サンプル紹介を参照してほしい。

公開API

CreateAppContainerProfile

MSDN参照

pszAppContainerNameの名前でAppContainerを作成する。
必要なくなったらDeleteAppContainerProfileで削除すること。
AppContainerRegisterSidと違いsidはシステム側で適切に生成してくれる様子。
すでに存在する場合は失敗する、生成済みかを判別する方法は特にない様子。
pszAppContainerNameはAppContainerRegisterSidでいうmonikerだと思われる。
pCapabilitiesは指定しなくともUpdateProcThreadAttributeで指定してあれば有効になる、というかpCapabilitiesを指定して何が変わるのかわからなかった、通常のモダンアプリなどを起動する際に付与するCapabilitiesを算出するためのものだろうか?
こちらはAppContainerRegisterSidと違ってMSDNにも載っておりsidの適切な生成などがあるので、一般にはこちらを使ってほしいという意図なのだと思われる。

サンプル

  const wchar_t * const name = L"AppConteinerTest";
  PSID sid;
  if (FAILED(::CreateAppContainerProfile(name, name, name, NULL, 0, &sid))) {
    return;
  }

DeleteAppContainerProfile

MSDN参照

指定したAppContainerを削除する。
GetAppContainerFolderPathで取得できるフォルダの中身や、GetAppContainerRegistryLocationで取得できるレジストリも消える。

サンプル

  const wchar_t * const name = L"AppConteinerTest";
  PSID sid;
  if (FAILED(::CreateAppContainerProfile(name, name, name, NULL, 0, &sid))) {
    return;
  }
  ...
  ::DeleteAppContainerProfile(name);

DeriveAppContainerSidFromAppContainerName

MSDN参照

pszAppContainerNameからCreateAppContainerProfile互換のsidを取得する。
AppContainerDeriveSidFromMonikerとほぼ同一の動作をする。
存在しないAppContainerに対しても実行でき、その場合はCreateAppContainerProfileで生成した場合に割り当てられるsidが返る。
取得したsidは必要なくなったらFreeSidで解放すること。

  PSID sid;
  if (FAILED(::DeriveAppContainerSidFromAppContainerName(name, &sid))) {
    return;
  }
  ...
  ::FreeSid(sid);

GetAppContainerFolderPath

MSDN参照

sidからアプリケーションフォルダを取得する。
アプリケーションフォルダ内はAppContainer下でも自由に読み書きでき、DeleteAppContainerProfileを呼び出さなければアプリ終了後にも残る。
逆にDeleteAppContainerProfileを呼ぶと、プロセスが生きていようともその時点でファイルが削除され読み書き権限もなくなる。
取得したpathは必要なくなったらCoTaskMemFreeで解放すること。
ちなみに手元だと
C:\Users\#{ユーザー名}\AppData\Local\Packages\#{AppContainer名}\AC
に保存されていた。

サンプル

  const wchar_t * const name = L"AppConteinerTest";
  PSID sid;
  if (FAILED(::CreateAppContainerProfile(name, name, name, NULL, 0, &sid))) {
    return;
  }
  wchar_t *sidStr;
  if (::ConvertSidToStringSidW(sid, &sidStr) == FALSE) {
    ::FreeSid(sid);
    return;
  }
  wchar_t *appContainerPath;
  if (FAILED(::GetAppContainerFolderPath(sidStr, &appContainerPath))) {
    ::FreeSid(sid);
    ::LocalFree(sidStr);
    return;
  }
  ...
  ::FreeSid(sid);
  ::LocalFree(sidStr);
  ::CoTaskMemFree(appContainerPath);

GetAppContainerRegistryLocation

MSDN参照

AppContainer下でも読み書きできるレジストリキーを取得する。
AppContainer下で実行されているプロセスでしか使えず、それ以外のプロセスで実行しても失敗する。
GetAppContainerFolderPathと同様にDeleteAppContainerProfileを呼び出さなければアプリ終了後にも残る。
逆にDeleteAppContainerProfileを呼ぶと、プロセスが生きていようともその時点でレジストリは削除され読み書き権限もなくなり、APIも失敗するようになる。
MSDNには記載がないが、おそらくRegCloseKeyで閉じる必要がある。
ちなみに、手元の環境だと
HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\#{AppContainer名}.#{乱数と思われるもの}
に保存されていた。

サンプル

  HKEY key;
  if (FAILED(::GetAppContainerRegistryLocation(KEY_ALL_ACCESS, &key))) {
    return;
  }
  ...
  ::RegCloseKey(key);

GetAppContainerNamedObjectPath

MSDN参照

AppContainer内プロセスで作成した各種ハンドルのリダイレクト先パス名を取得する。
外部のプロセスからこのパス名を付けてハンドルを作成することでAppContainer内部とやり取りすることができる。
手元の環境で試した範囲では「AppContainerNamedObjects\#{AppContainerのSID}」と帰ってくるようだった。
WinObjで具体的にどこにハンドルが属しているかを見ることができるので、興味があるなら見るのもよい。
詳しくはAppContainer関連サンプル紹介を参照。

サンプル

  PSID sid;
  if (FAILED(::DeriveAppContainerSidFromAppContainerName(name, &sid))) {
    return;
  }
  wchar_t buffer[4096];
  ULONG size;
  if (::GetAppContainerNamedObjectPath(NULL, sid, _countof(buffer), buffer, &size) == FALSE) {
    return;
  }
  ...
  ::FreeSid(sid);

非公開API

使用は推奨されないが、一応非公開APIについても調べたので書いておく。

AppContainerRegisterSid

HRESULT WINAPI AppContainerRegisterSid(PSID sid, LPCWSTR moniker, LPCWSTR display_name);

非公開API、GetProcAddressして使う。
sidをAppContainerとして登録する。
必要なくなったらAppContainerUnregisterSidで削除すること。
CreateAppContainerProfileで作成した場合と違いアプリケーションフォルダやレジストリへのアクセス権限は付与されないので注意。
sidやmonikerがすでに存在する場合は失敗する、すでに存在するか調べたい場合はAppContainerLookupMonikerを使用すること。
win8だとkernel32.dllにいるが8.1でkernelbase.dllに移った、おそらくVirtulDllで何か仮想dllに紐づいていてその実態が移動したとかだと思われる。
ぐぐるとわかるのだがchromiumのソースで使われていたことがある、というかほかに情報がない。

サンプル

  const wchar_t * const appContainerSid = L"S-1-15-2-1234567890-1234567890-1234567890-123456789-1234567890-123456789-1234567890";
  const wchar_t * const name = L"AppConteinerTest";
  typedef HRESULT(WINAPI* AppContainerRegisterSidPtr)(PSID sid, LPCWSTR moniker, LPCWSTR display_name);
  AppContainerRegisterSidPtr AppContainerRegisterSid = reinterpret_cast<AppContainerRegisterSidPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerRegisterSid"));
  if (AppContainerRegisterSid == NULL) {
    AppContainerRegisterSid = reinterpret_cast<AppContainerRegisterSidPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerRegisterSid"));
    if (AppContainerRegisterSid == NULL) {
      return false;
    }
  }
  PSID sid;
  if (::ConvertStringSidToSidW(appContainerSid, &sid) == FALSE) {
    return false;
  }
  if (FAILED(AppContainerRegisterSid(sid, name, name))) {
    return false;
  }

AppContainerUnregisterSid

HRESULT WINAPI AppContainerUnregisterSid(PSID sid);

非公開API、GetProcAddressして使う。
sidでAppContainerに登録されている情報を削除する。
AppContainerRegisterSidと同様にwin8だとkernel32.dllにいるが8.1でkernelbase.dllに移った。

サンプル

  typedef HRESULT(WINAPI* AppContainerUnregisterSidPtr)(PSID sid);
  AppContainerUnregisterSidPtr AppContainerUnregisterSid = reinterpret_cast<AppContainerUnregisterSidPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerUnregisterSid"));
  if (AppContainerUnregisterSid == NULL) {
    AppContainerUnregisterSid = reinterpret_cast<AppContainerUnregisterSidPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerUnregisterSid"));
    if (AppContainerUnregisterSid == NULL) {
      return;
    }
  }
  AppContainerUnregisterSid(sid);

AppContainerLookupMoniker

HRESULT WINAPI AppContainerLookupMoniker(PSID sid, LPWSTR* moniker);

非公開API、GetProcAddressして使う。
sidで登録されているAppContainerのmonikerを取得する。
sidに紐づくAppContainerが存在しない場合は失敗する。
monikerにはAPI内で動的に確保されたメモリーが割り当てられるため、必要なくなったらAppContainerFreeMemoryで解放すること。
AppContainerRegisterSidと同様にwin8だとkernel32.dllにいるが8.1でkernelbase.dllに移った。

サンプル

  typedef HRESULT(WINAPI* AppContainerLookupMonikerPtr)(PSID sid, LPWSTR* moniker);
  AppContainerLookupMonikerPtr AppContainerLookupMoniker = reinterpret_cast<AppContainerLookupMonikerPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerLookupMoniker"));
  if (AppContainerLookupMoniker == NULL) {
    AppContainerLookupMoniker = reinterpret_cast<AppContainerLookupMonikerPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerLookupMoniker"));
    if (AppContainerLookupMoniker == NULL) {
      return;
    }
  }
  typedef BOOLEAN(WINAPI* AppContainerFreeMemoryPtr)(void* ptr);
  AppContainerFreeMemoryPtr AppContainerFreeMemory = reinterpret_cast<AppContainerFreeMemoryPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerFreeMemory"));
  if (AppContainerFreeMemory == NULL) {
    AppContainerFreeMemory = reinterpret_cast<AppContainerFreeMemoryPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerFreeMemory"));
    if (AppContainerFreeMemory == NULL) {
      return;
    }
  }
  wchar_t *moniker;
  if (FAILED(AppContainerLookupMoniker(sid, &moniker))) {
    return;
  }
  ...
  AppContainerFreeMemory(moniker);

AppContainerDeriveSidFromMoniker

HRESULT WINAPI AppContainerDeriveSidFromMoniker(LPCWSTR moniker, PSID *sid);

非公開API、GetProcAddressして使う。
monikerからCreateAppContainerProfile互換のsidを取得する。
存在しないAppContainerに対しても実行でき、その場合はCreateAppContainerProfileで生成した場合に割り当てられるsidが返る。
どうやらmonikerをハッシュ関数に食わせるなどで生成しているようで、AppContainerRegisterSidで登録したmonikerを指定してもまるで違うsidが返ってくるので注意。
取得したsidは必要なくなったらAppContainerFreeMemoryで解放すること。
AppContainerRegisterSidと同様にwin8だとkernel32.dllにいるが8.1でkernelbase.dllに移った。

  typedef HRESULT(WINAPI* AppContainerDeriveSidFromMonikerPtr)(LPCWSTR moniker, PSID *sid);
  AppContainerDeriveSidFromMonikerPtr AppContainerDeriveSidFromMoniker = reinterpret_cast<AppContainerDeriveSidFromMonikerPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerDeriveSidFromMoniker"));
  if (AppContainerDeriveSidFromMoniker == NULL) {
    AppContainerDeriveSidFromMoniker = reinterpret_cast<AppContainerDeriveSidFromMonikerPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerDeriveSidFromMoniker"));
    if (AppContainerDeriveSidFromMoniker == NULL) {
      return;
    }
  }
  PSID sid;
  if (FAILED(AppContainerDeriveSidFromMoniker(moniker, &sid))) {
    return;
  }
  ...
  AppContainerFreeMemory(sid);

AppContainerFreeMemory

BOOLEAN WINAPI AppContainerFreeMemory(void* ptr);

非公開API、GetProcAddressして使う。
AppContainer関連APIで確保したメモリーを開放する。
AppContainerRegisterSidと同様にwin8だとkernel32.dllにいるが8.1でkernelbase.dllに移った。

サンプル

  typedef BOOLEAN(WINAPI* AppContainerFreeMemoryPtr)(void* ptr);
  AppContainerFreeMemoryPtr AppContainerFreeMemory = reinterpret_cast<AppContainerFreeMemoryPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerFreeMemory"));
  if (AppContainerFreeMemory == NULL) {
    AppContainerFreeMemory = reinterpret_cast<AppContainerFreeMemoryPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerFreeMemory"));
    if (AppContainerFreeMemory == NULL) {
      return;
    }
  }
  AppContainerFreeMemory(ptr);

AppContainerでデスクトップアプリを起動してみた

Filed under: 未分類 — 中の人 @ 2:54 AM

Windows8以降にAppContainerというsandbox機能が導入されているのをご存じだろうか?
このAppContainerは主にWindowsストアアプリの実行環境として使用されているのだが、最近巷を騒がせているIEの脆弱性もAppContainerを有効にすることで回避することができるらしいと聞く。
ということは、デスクトップアプリであってもAppContainerでセキュリティが向上できるかもしれない。
そこで今回はAppContainer上でデスクトップアプリを実行する方法を解説してみたいと思う。

本記事で紹介するコードやコンパイル済みのexeなどを用意しておいたので、実際に動かしてみたい方はどうぞ。
ただし、後始末やここでは解説しない機能の使用なども含んでいるため、本質的ではないコードが多いことをあらかじめ断っておく。
http://wordpress.click3.org/garakuta/StartAppContainer.zip

割り当てるCapabilityを取得する

AppContainer上で動作するアプリは既存のIntegrity Levelによる制限とは違い、Capabilityとして宣言しておくことで追加の権限を得ることができる。
そこでまずは動作させたいアプリに付与する権限を取得するところから始めよう。
なお、具体的にどのような権限があるかは解説しないので、詳しくはMSDNを参照してほしい。

実際にCapabilityを取得するコードは以下のようになる。

bool SetCapability(const WELL_KNOWN_SID_TYPE type, std::vector<SID_AND_ATTRIBUTES> &list, std::vector<SHARED_SID> &sidList) {
  SHARED_SID capabilitySid(new unsigned char[SECURITY_MAX_SID_SIZE]);
  DWORD sidListSize = SECURITY_MAX_SID_SIZE;
  if (::CreateWellKnownSid(type, NULL, capabilitySid.get(), &sidListSize) == FALSE) {
    return false;
  }
  if (::IsWellKnownSid(capabilitySid.get(), type) == FALSE) {
    return false;
  }
  SID_AND_ATTRIBUTES attr;
  attr.Sid = capabilitySid.get();
  attr.Attributes = SE_GROUP_ENABLED;
  list.push_back(attr);
  sidList.push_back(capabilitySid);
  return true;
}

前後にごちゃごちゃ長いものがついているが、本質的にはCreateWellKnownSidでSIDを取得し、SID_AND_ATTRIBUTES構造体に詰めて返している。
IsWellKnownSidはSIDがtypeと一致するかを調べるもので、間違いなく成功するので意味はないassertの一種だと思ってほしい。
その他はメモリーの開放といった後始末になる。

まずCreateWellKnownSidはシステム定義のSIDを取得するAPIでCapabilityのSIDもこれで取得できる。
取得するSIDの種類を表すWELL_KNOWN_SID_TYPEのうちWinCapabilityから始まるものがそれだ。

次にSID_AND_ATTRIBUTESだが、これは何のためのSIDなのかやどういう意図で使用するかなどを表す。
今回はCapabilityを追加したいのでAttributesにSE_GROUP_ENABLEDを設定したものを使う。

AppContainerのプロファイルを作成する

次はAppContainerのプロファイルを作成しよう。
プロファイルも識別子としてはSIDを用いるので、今回取得するのもSIDとなる。

  PSID sidImpl;
  DeleteAppContainer deleteAppContainer(AppContainerName);
  if (FAILED(::CreateAppContainerProfile(AppContainerName, AppContainerName, AppContainerName, (capabilities.empty() ? NULL : &capabilities.front()), capabilities.size(), &sidImpl))) {
    return false;
  }
  const SHARED_SID sid = ToSharedSID2(sidImpl);

CreateAppContainerProfileがプロファイルを作成するAPIで、その前後は後始末に関係する。
CreateAppContainerProfileのプロトタイプは以下のようになる。

HRESULT WINAPI CreateAppContainerProfile(
  _In_   PCWSTR pszAppContainerName,
  _In_   PCWSTR pszDisplayName,
  _In_   PCWSTR pszDescription,
  _In_   PSID_AND_ATTRIBUTES pCapabilities,
  _In_   DWORD dwCapabilityCount,
  _Out_  PSID *ppSidAppContainerSid
);

第一引数はプロファイルの識別子で、AppContainer固有のアプリケーションフォルダのパスなどで使用される。
第二第三は表示用なので割愛。
第四第五引数はCapabilityを指定するもののようだが、ここでは指定しなくとも問題なく権限は付加される。
おそらくはモダンアプリなどexplorerから起動出来るAppContainerのアプリの場合、ここで設定したCapabilityを用いて起動するのだと思われる。
コード中で指定してあるのはあくまで念のためだ。
第六引数は生成したプロファイルのSIDを受けるポインターとなる。

作成したプロファイルでアプリを起動する

さて、プロファイルも無事生成出来たのでいよいよアプリを実行しよう。
少々長いが、アプリの起動は以下のようになる。

  const unsigned int attrCount = 3;
  SIZE_T size;
  ::InitializeProcThreadAttributeList(NULL, attrCount, 0, &size);
  const SHARED_ATTR_LIST attrList = ToSharedAttributeList(reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(new unsigned char[size]));
  if (::InitializeProcThreadAttributeList(attrList.get(), attrCount, 0, &size) == FALSE) {
    return false;
  }
  SECURITY_CAPABILITIES sc;
  sc.AppContainerSid = sid.get();
  sc.Capabilities = (capabilities.empty() ? NULL : &capabilities.front());
  sc.CapabilityCount = capabilities.size();
  sc.Reserved = 0;
  if (::UpdateProcThreadAttribute(attrList.get(), 0, PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, &sc, sizeof(sc), NULL, NULL) == FALSE) {
    return false;
  }
  STARTUPINFOEXW startupInfoEx = { 0 };
  startupInfoEx.StartupInfo.cb = sizeof(STARTUPINFOEXW); // StartupInfoのサイズではなくstartupInfoExのサイズである点に注意
  startupInfoEx.lpAttributeList = attrList.get();
  PROCESS_INFORMATION pi = { 0 };
  if (::CreateProcessW(path.wstring().c_str(), NULL, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, reinterpret_cast<LPSTARTUPINFO>(&startupInfoEx), &pi) == FALSE) {
    std::wcout << GetLastError() << std::endl;
    return false;
  }
  ::WaitForSingleObject(pi.hProcess, INFINITE);
  ::CloseHandle(pi.hThread);
  ::CloseHandle(pi.hProcess);

InitializeProcThreadAttributeListでLPPROC_THREAD_ATTRIBUTE_LISTを初期化し、UpdateProcThreadAttributeでプロファイルのsidとCapabilityを設定してCreateProcessWでアプリを起動している。
その他諸々はほかと同じく後始末になる。

InitializeProcThreadAttributeListは見たまま単に初期化を行っているだけだ。
設定する属性の数は念のため3にしてあるが、おそらく1でも動作する。
UpdateProcThreadAttributeもみたままにLPPROC_THREAD_ATTRIBUTE_LISTにsidを設定しているだけだ。
Capabilityはここで指定してあればプロファイルに指定がなくともちゃんと割り振られて動作する。
CreateProcessWはEXTENDED_STARTUPINFO_PRESENTを指定してやるとSTARTUPINFOでLPPROC_THREAD_ATTRIBUTE_LISTを受けてくれるようになる。
あとは単にSTARTUPINFOEXWにLPPROC_THREAD_ATTRIBUTE_LISTを設定して起動しているだけだ。

実際に起動してみる

http://wordpress.click3.org/garakuta/StartAppContainer.zipに完成品があるのでDLしてもらって、中のTestAppContainer.exeを普通にダブルクリックで起動してみてほしい。
どうだろうか?おそらくは「このアプリケーションはアプリ コンテナーのコンテキストでのみ実行できます。」と表示されて起動しなかったと思う。
これは/APPCONTAINERオプションを指定してコンパイルされたものでAppContainer以外では動作しない。
そこで次はTestAppContainer2.exeを起動してみてほしい。
こちらは単に/APPCONTAINERオプションを外しただけのものになる。
動作としてはいろいろなフォルダにアクセスして中身を列挙するというものだ。

では完成品であるStartAppContainer.exeにTestAppContainer.exeをドラッグ&ドロップしてみてほしい。
今度はちゃんと起動したと思う。
また動作もTestAppContainer2.exeを起動したときと違い、見えないフォルダがあったり、そもそも別フォルダを指していたりしたと思う。
それがAppContainerによるサンドボックス化された環境ということになる。

また、付与したCapabilityによる差が見たい人はTestWinRT.exeを試してみてほしい。
こちらはWinRTのAPIを用いている以外はTestAppContainer.exeと同様だが、AppContainerとして起動するとミュージックフォルダが取得できるようになっているはずだ。
これはCapabilityでMusicLibraryを付与したためにアクセス可能になったことを表している。
TestAppContainer.exeで見えていないのはCapabilityがあくまでWinRTのAPIにしか影響しないためだ。

その他cmd.exeをAppContainerで起動してみるなどもできるのでいろいろ試してみてほしい。

終わりに

どうだっただろうか。
今回はあくまでAppContainerで起動するところまでを目標としたが、zipの中では紹介していないAPIを使用するコードなども入れてあり、また次の記事では関連APIについてもまとめているので、興味があれば見ていってほしい。

最初にも書いたがIEもAppContainerにより脆弱性が見つかってもできるだけ他所に波及しないようにと作られている。
この記事を読んでそういうことに興味を持ってくれる人が一人でも増えたら幸いだ。

ではまた何か機会があればその時に。

2014 年 3 月 23 日

th135_ai ver1.02bリリース

Filed under: 未分類 — 中の人 @ 9:17 PM

http://wordpress.click3.org/garakuta/th135_ai.zip

・AIツールを起動したあと一回目に限り1P側が霊夢に変わってしまう不具合を修正。

共有メモリーにてやり取りしているわけですが、AIツールは二個起動させることも想定しているとか
AIか心綺楼かどちらかが生き残っている限り中身は残り続けるなどがあるため、共有メモリーを作成したほうが初期化しなければなりません。
そこのAI側の初期化にバグがあり、使用キャラ切り替え部分が未初期化だったわけです。

というわけでバグ修正のみです。
なので、ツールスレなどには出さずにここでひっそりと公開するだけにとどめておきます。

2014 年 2 月 5 日

th135_ai ver1.02aリリース

Filed under: 未分類 — 中の人 @ 4:02 AM

http://wordpress.click3.org/garakuta/th135_ai.zip

何かバグ修正したとか機能追加したとかではなく、内部で使っているmrubyのバージョンを上げただけ。
その関係でもしかしたら何かバグがなおっていたり機能が追加されていたり早くなったりしているかもしれない。
というわけで、ツールスレなどには出さずにここでひっそりと公開するだけにとどめておきます。

2014 年 1 月 16 日

th123_ai ver0.96リリース

Filed under: 未分類 — 中の人 @ 1:33 AM

http://wordpress.click3.org/garakuta/th123_ai.zip

ツールスレにて、実に一年以上前ですが要望の書き込みがあったので、今更ながら対応したのが今回のリリースになります。
また開発環境の作り直しの都合で、VisualStudioがまたバージョン変わったり、その影響でXP向けにもビルドできるようになったので余分なラッパー外したり、なんてことをしてました。
ちなみに、各種フラグは内部的には普通に持っていて、単に表に出していなかっただけという手抜きぶり。
なんで要望当時の自分は対応しなかったんでしょうね。

Older Posts »

Powered by WordPress