Source code

Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "sandboxing.h"
#include <memory>
#include <windows.h>
#include <sddl.h>
#include <userenv.h>
#include <QProcess>
#include <QSettings>
/** RAII wrapper of STARTUPINFOEX. */
struct StartupInfoExWrap
{
STARTUPINFOEX si = {};
StartupInfoExWrap ()
{
si.StartupInfo.cb = sizeof(STARTUPINFOEX);
const DWORD attr_count = 1; // SECURITY_CAPABILITIES
SIZE_T attr_size = 0;
InitializeProcThreadAttributeList(NULL, attr_count, 0, &attr_size);
si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)new BYTE[attr_size]();
if (!InitializeProcThreadAttributeList(si.lpAttributeList, attr_count, 0, &attr_size))
qFatal("InitializeProcThreadAttributeList failed");
}
void SetSecurity(SECURITY_CAPABILITIES *sc)
{
if (!UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES,
sc, sizeof(SECURITY_CAPABILITIES), NULL, NULL))
qFatal("UpdateProcThreadAttribute failed");
}
~StartupInfoExWrap()
{
DeleteProcThreadAttributeList(si.lpAttributeList);
delete [] (BYTE*)si.lpAttributeList;
}
};
/** RAII wrapper of PROCESS_INFORMATION. */
struct ProcessInformationWrap
{
PROCESS_INFORMATION pi = {};
~ProcessInformationWrap()
{
CloseHandle(pi.hThread);
pi.hThread = nullptr;
CloseHandle(pi.hProcess);
pi.hProcess = nullptr;
}
};
/** RAII class for temporarily impersonating an AppContainer-isolated process
* for the current thread. Intended to be used together with CLSCTX_ENABLE_CLOAKING
* when creating COM objects. There's no direct support for AppContainer
* impersonation in Windows, so the impl. will instead create a suspended throw-away
* process within the AppContainer to use as basis for AppContainer impersonation.
* This seem kind of weird, but is the approach recommended by Microsoft when opening
* a support case on the matter. Based on "AppContainer Isolation"
struct AppContainer : public Sandboxing
{
AppContainer(const QString &clsid)
{
// Create AppContainer sandbox without any special capabilities
static const wchar_t name[] = L"Qt_testcon";
static const wchar_t desc[] = L"Qt ActiveQt Test Container";
HRESULT hr = CreateAppContainerProfile(name, name, desc, nullptr, 0, &m_sid);
if (HRESULT_CODE(hr) == ERROR_ALREADY_EXISTS)
hr = DeriveAppContainerSidFromAppContainerName(name, &m_sid); // fallback to existing container
if (FAILED(hr))
qFatal("CreateAppContainerProfile and DeriveAppContainerSidFromAppContainerName failed");
SECURITY_CAPABILITIES sec_cap = {};
sec_cap.AppContainerSid = m_sid;
StartupInfoExWrap si;
si.SetSecurity(&sec_cap);
// Create suspended COM server process in AppContainer
QString exe_path = GetExePath(clsid);
ProcessInformationWrap pi;
DWORD flags = EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED; // don't start main thread
if (!CreateProcess(exe_path.toStdWString().data(), nullptr, nullptr, nullptr,
FALSE, flags, nullptr, nullptr, (STARTUPINFO*)&si.si, &pi.pi))
qFatal("CreateProcess failed");
// Kill process since we're only interested in the handle for now.
// The COM runtime will later recreate the process when calling CoCreateInstance.
TerminateProcess(pi.pi.hProcess, 0);
// Create AppContainer impersonation token
HANDLE cur_token = nullptr;
if (!OpenProcessToken(pi.pi.hProcess,
TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY,
&cur_token))
qFatal("OpenProcessToken failed");
if (!DuplicateTokenEx(cur_token, 0, NULL, SecurityImpersonation, TokenPrimary, &m_token))
qFatal("DuplicateTokenEx failed");
CloseHandle(cur_token);
cur_token = nullptr;
// Impersonate AppContainer on current thread
if (!ImpersonateLoggedOnUser(m_token))
qFatal("ImpersonateLoggedOnUser failed");
}
~AppContainer()
{
if (!RevertToSelf())
qFatal("RevertToSelf failed");
CloseHandle(m_token);
m_token = nullptr;
FreeSid(m_sid);
m_sid = nullptr;
}
private:
/** Get EXE path for a COM class. Input is on "{hex-guid}" format.
* Returns empty string if the COM class is DLL-based and on failure. */
static QString GetExePath (const QString &clsid)
{
// extract COM class
QSettings cls_folder("HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID\\" + clsid, QSettings::NativeFormat);
QString command = cls_folder.value("LocalServer32/.").toString();
if (command.isEmpty())
return ""; // not exe path found
// remove any quotes and "/automation" or "-activex" arguments
QStringList arguments = QProcess::splitCommand(command);
return arguments[0]; // executable is first argument
}
PSID m_sid = nullptr;
HANDLE m_token = nullptr;
};
/** RAII class for temporarily impersonating low-integrity level for the current thread.
Intended to be used together with CLSCTX_ENABLE_CLOAKING when creating COM objects.
Based on "Designing Applications to Run at a Low Integrity Level" https://msdn.microsoft.com/en-us/library/bb625960.aspx */
class LowIntegrity : public Sandboxing
{
public:
LowIntegrity()
{
HANDLE cur_token = nullptr;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY, &cur_token))
qFatal("OpenProcessToken failed");
if (!DuplicateTokenEx(cur_token, 0, nullptr, SecurityImpersonation, TokenPrimary, &m_token))
qFatal("DuplicateTokenEx failed");
CloseHandle(cur_token);
PSID li_sid = nullptr;
if (!ConvertStringSidToSid(L"S-1-16-4096", &li_sid)) // low integrity SID
qFatal("ConvertStringSidToSid failed");
// reduce process integrity level
TOKEN_MANDATORY_LABEL TIL = {};
TIL.Label.Attributes = SE_GROUP_INTEGRITY;
TIL.Label.Sid = li_sid;
if (!SetTokenInformation(m_token, TokenIntegrityLevel, &TIL, sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(li_sid)))
qFatal("SetTokenInformation failed");
if (!ImpersonateLoggedOnUser(m_token)) // change current thread integrity
qFatal("ImpersonateLoggedOnUser failed");
LocalFree(li_sid);
li_sid = nullptr;
}
~LowIntegrity()
{
if (!RevertToSelf())
qFatal("RevertToSelf failed");
CloseHandle(m_token);
m_token = nullptr;
}
private:
HANDLE m_token = nullptr;
};
std::unique_ptr<Sandboxing> Sandboxing::Create(QAxSelect::SandboxingLevel level, const QString &clsid)
{
if (level == QAxSelect::SandboxingLowIntegrity)
return std::make_unique<LowIntegrity>();
else if (level == QAxSelect::SandboxingAppContainer)
return std::make_unique<AppContainer>(clsid);
Q_ASSERT_X(false, "Sandboxing::Create", "unknown sandboxing level");
return {};
}