Low Integrity Processでのテンポラリディレクトリ

Windows Vistaで導入されたLow Integrity Process(低整合性プロセス)は、主に、Internet Explorerなどで使われていますが、それ以外にも、prevhost.exe (エクスプローラのプレビューアを隔離するプロセス)で利用されていたり、Designing Applications to Run at a Low Integrity Levelで示されている方法によって、様々なアプリを低整合性プロセスとして実行したりすることすら可能です。

GetTempPath/GetTempFileName

低整合性プロセスにおいては、%userprofile%\AppData\Local\Tempには書き込み権限がなかったりします。にもかかわらず、GetTempPathは依然として、%userprofile%\AppData\Local\Tempを返してくれます。その結果、そのパスを使って、GetTempFileNameを呼び出すと、ERROR_ACCESS_DENIEDでエラーになってしまいます。
UNIX系から移植されたコードでも、環境変数TMP/TEMPの挙動に依存していたりするので、状況はあまり変わりません。
そこで、GetTempPath/TMP/TEMPが返してくれるパスを書き換えてしまうのが得策ですが、GetTempPathの説明には、環境変数TMP/TEMPへの言及があるので、低整合性プロセスであることが確実な場合には、TMP/TEMPを%userprofile%\AppData\LocalLow内のディレクトリに置き換えればよいようです。

低整合性プロセスかどうかを確認する

いずれにしても、最初にやるべきことは、低整合性プロセスかどうかを判別することでしょう。これは、Appendix D: Getting the Integrity Level for an Access Tokenにそのまんま利用できるコードがサンプルとして提供されています。

コード

上記、すべてを踏まえた上で、低整合性プロセスでのテンポラリフォルダ設定を行うサンプルコードを書いてみました。

このコードを普通に実行すれば、%userprofile%\AppData\Local\Tempに該当するディレクトリを表示しますが、低整合性プロセスの場合には、%userprofile%\AppData\LocalLow\Tempを表示します。

#include <stdio.h>
#include <windows.h>
#include <shlwapi.h>
#include <shlobj.h>

const DWORD INVALID_RID = 0xffffffff;

DWORD GetProcessIntegrityLevel()
{
  
  HANDLE hToken;
  if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
    return INVALID_RID;
    
  // Get the Integrity level.
  DWORD dwLengthNeeded;
  GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &dwLengthNeeded);
  if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  {
    CloseHandle(hToken);
    return INVALID_RID; //???
  }
  
  PTOKEN_MANDATORY_LABEL pTIL = (PTOKEN_MANDATORY_LABEL)new BYTE[dwLengthNeeded];
  if(!GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, dwLengthNeeded, &dwLengthNeeded))
  {
    delete[] (BYTE*)pTIL;
    CloseHandle(hToken);
    return INVALID_RID;
  }

  DWORD dwIntegrityLevel = *GetSidSubAuthority(pTIL->Label.Sid, 
    (DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid)-1));
  delete[] (BYTE*)pTIL;
  CloseHandle(hToken);
  return dwIntegrityLevel;
}

// the string should be released with CoTaskMemFree
PWSTR GetLowIntegrityTempPath()
{
  HMODULE hShell32 = LoadLibraryW(L"shell32.dll");
  if(!hShell32)
    return NULL;
  
  typedef HRESULT (WINAPI *SHGetKnownFolderPathFunc)(
    const GUID& /*REFKNOWNFOLDERID*/ refid,
    DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath);

  SHGetKnownFolderPathFunc getKnownFolderPath
    = (SHGetKnownFolderPathFunc)GetProcAddress(hShell32,
      "SHGetKnownFolderPath");
  if(!getKnownFolderPath)
  {
    FreeLibrary(hShell32);
    return NULL;
  }
  
  PWSTR lowTemp = NULL;
  if(FAILED(getKnownFolderPath(FOLDERID_LocalAppDataLow, 0, NULL, &lowTemp)))
  {
    FreeLibrary(hShell32);
    return NULL;
  }
  
  FreeLibrary(hShell32);
  return lowTemp;
}

bool LowIntegrityStartup()
{
  DWORD integrityLevel = GetProcessIntegrityLevel();
  if(integrityLevel == INVALID_RID)
    return false;
  
  if(integrityLevel != SECURITY_MANDATORY_LOW_RID)
    return true;
  
  PWSTR temp = GetLowIntegrityTempPath();
  if(!temp)
    return false;
  
  WCHAR buf[MAX_PATH];
  wcscpy_s(buf, MAX_PATH, temp);
  wcscat_s(buf, MAX_PATH, L"\\Temp");
  SHCreateDirectoryExW(NULL, buf, NULL);
  SetEnvironmentVariableW(L"TMP", buf);
  SetEnvironmentVariableW(L"TEMP", buf);
  CoTaskMemFree(temp);
  return true;
}

int main()
{
  LowIntegrityStartup();
  
  TCHAR tmp[MAX_PATH];
  GetTempPath(MAX_PATH, tmp);
  printf("TEMP: %s\n", tmp);
}