summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFredrik Orderud <fredrik.orderud@ge.com>2022-11-28 13:49:44 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-12-15 18:45:09 +0000
commit12f159f85bf025c117f8358dbc07f39a6e5a0bf1 (patch)
tree910d4efcf9c5e2aef275936590efb113efb6c138
parentc28c52d418834f107c41632273dcf8fbb32b15aa (diff)
Extend TestCon with AppContainer sandboxing
TestCon already support starting OLE controls in a "regular" separate process and in a low integrity level (low IL) process. This commit will extend QAxWidget::setClassContext and the impersonation support to also allow starting OLE controls in a AppContainer-isolated process for enhanced security sandboxing. Task-number: QTBUG-109184 Change-Id: I525e99ba6934025710bbc97fba0045d0358413b7 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io> (cherry picked from commit 55876d1b4e6a98d8a6ea7a03a0ef3288df4c84e7) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/activeqt/container/qaxselect.cpp6
-rw-r--r--src/activeqt/container/qaxselect.h1
-rw-r--r--tools/testcon/mainwindow.cpp2
-rw-r--r--tools/testcon/sandboxing.cpp140
-rw-r--r--tools/testcon/sandboxing.h3
5 files changed, 148 insertions, 4 deletions
diff --git a/src/activeqt/container/qaxselect.cpp b/src/activeqt/container/qaxselect.cpp
index e53def7..b8b2c69 100644
--- a/src/activeqt/container/qaxselect.cpp
+++ b/src/activeqt/container/qaxselect.cpp
@@ -30,6 +30,7 @@ QT_BEGIN_NAMESPACE
\value SandboxingNone No specific sandboxing desired
\value SandboxingProcess Run ActiveX control in a separate process
\value SandboxingLowIntegrity Run ActiveX control in a separate low-integrity process
+ \value SandboxingAppContainer Run ActiveX control in a separate AppContainer-isolated process
Sandboxing requires that the ActiveX is either built as an EXE, or as a DLL with AppID "DllSurrogate" enabled.
*/
@@ -327,7 +328,8 @@ QAxSelect::QAxSelect(QWidget *parent, Qt::WindowFlags flags)
d->filterModel->setSourceModel(new ControlList(this));
d->selectUi.ActiveXList->setModel(d->filterModel);
- QStringList sandboxingOptions = { QLatin1String("None"), QLatin1String("Process isolation"), QLatin1String("Low integrity process") };
+ QStringList sandboxingOptions = { QLatin1String("None"), QLatin1String("Process isolation"),
+ QLatin1String("Low integrity process"), QLatin1String("AppContainer process") };
d->selectUi.SandboxingCombo->addItems(sandboxingOptions);
connect(d->selectUi.ActiveXList->selectionModel(), &QItemSelectionModel::currentChanged,
@@ -373,6 +375,8 @@ QAxSelect::SandboxingLevel QAxSelect::sandboxingLevel() const
return SandboxingProcess;
case 2:
return SandboxingLowIntegrity;
+ case 3:
+ return SandboxingAppContainer;
default:
break;
}
diff --git a/src/activeqt/container/qaxselect.h b/src/activeqt/container/qaxselect.h
index 3e10485..5146d5e 100644
--- a/src/activeqt/container/qaxselect.h
+++ b/src/activeqt/container/qaxselect.h
@@ -18,6 +18,7 @@ public:
SandboxingNone = 0,
SandboxingProcess,
SandboxingLowIntegrity,
+ SandboxingAppContainer,
};
explicit QAxSelect(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
diff --git a/tools/testcon/mainwindow.cpp b/tools/testcon/mainwindow.cpp
index 482840e..df81e42 100644
--- a/tools/testcon/mainwindow.cpp
+++ b/tools/testcon/mainwindow.cpp
@@ -107,7 +107,7 @@ bool MainWindow::addControlFromClsid(const QString &clsid, QAxSelect::Sandboxing
break;
default:
// impersonate desired sandboxing
- sandbox_impl = Sandboxing::Create(sandboxing);
+ sandbox_impl = Sandboxing::Create(sandboxing, clsid);
// require out-of-process and activate impersonation
container->setClassContext(CLSCTX_LOCAL_SERVER | CLSCTX_ENABLE_CLOAKING);
break;
diff --git a/tools/testcon/sandboxing.cpp b/tools/testcon/sandboxing.cpp
index 4774833..e0c0eea 100644
--- a/tools/testcon/sandboxing.cpp
+++ b/tools/testcon/sandboxing.cpp
@@ -6,8 +6,144 @@
#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"
+ * https://learn.microsoft.com/en-us/windows/win32/secauthz/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 */
@@ -56,10 +192,12 @@ private:
HANDLE m_token = nullptr;
};
-std::unique_ptr<Sandboxing> Sandboxing::Create(QAxSelect::SandboxingLevel level)
+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 {};
diff --git a/tools/testcon/sandboxing.h b/tools/testcon/sandboxing.h
index d63d746..5f51227 100644
--- a/tools/testcon/sandboxing.h
+++ b/tools/testcon/sandboxing.h
@@ -3,13 +3,14 @@
#ifndef SANDBOXING_H
#define SANDBOXING_H
+#include <QString>
#include <QAxSelect>
class Sandboxing
{
public:
- static std::unique_ptr<Sandboxing> Create(QAxSelect::SandboxingLevel level);
+ static std::unique_ptr<Sandboxing> Create(QAxSelect::SandboxingLevel level, const QString &clsid);
Sandboxing() {}