diff --git a/patch.diff b/patch.diff deleted file mode 100644 index 6f59399e3..000000000 --- a/patch.diff +++ /dev/null @@ -1,896 +0,0 @@ -diff --git a/src/cascadia/LocalTests_SettingsModel/pch.h b/src/cascadia/LocalTests_SettingsModel/pch.h -index 53f6145d2..01b4cdfe8 100644 ---- a/src/cascadia/LocalTests_SettingsModel/pch.h -+++ b/src/cascadia/LocalTests_SettingsModel/pch.h -@@ -61,6 +61,9 @@ Author(s): - // Manually include til after we include Windows.Foundation to give it winrt superpowers - #include "til.h" - -+#include -+#include -+ - // Common includes for most tests: - #include "../../inc/argb.h" - #include "../../inc/conattrs.hpp" -diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp -index 6d53a6ba7..38edbc2c9 100644 ---- a/src/cascadia/TerminalApp/AppLogic.cpp -+++ b/src/cascadia/TerminalApp/AppLogic.cpp -@@ -189,7 +210,7 @@ namespace winrt::TerminalApp::implementation - } - - AppLogic::AppLogic() : -- _reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } } -+ _reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); ElevatedState::SharedInstance().Reload(); } } - { - // For your own sanity, it's better to do setup outside the ctor. - // If you do any setup in the ctor that ends up throwing an exception, -@@ -906,8 +927,6 @@ namespace winrt::TerminalApp::implementation - void AppLogic::_RegisterSettingsChange() - { - const std::filesystem::path settingsPath{ std::wstring_view{ CascadiaSettings::SettingsPath() } }; -- const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } }; -- - _reader.create( - settingsPath.parent_path().c_str(), - false, -@@ -916,14 +935,17 @@ namespace winrt::TerminalApp::implementation - // editors, who will write a temp file, then rename it to be the - // actual file you wrote. So listen for that too. - wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime, -- [this, settingsBasename = settingsPath.filename(), stateBasename = statePath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) { -+ [this, settingsPath](wil::FolderChangeEvent, PCWSTR fileModified) { -+ static const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } }; -+ static const std::filesystem::path elevatedStatePath{ std::wstring_view{ ElevatedState::SharedInstance().FilePath() } }; -+ - const auto modifiedBasename = std::filesystem::path{ fileModified }.filename(); - -- if (modifiedBasename == settingsBasename) -+ if (modifiedBasename == settingsPath.filename()) - { - _reloadSettings->Run(); - } -- else if (modifiedBasename == stateBasename) -+ else if (modifiedBasename == statePath.filename() || modifiedBasename == elevatedStatePath.filename()) - { - _reloadState(); - } -diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp -index 5a00ba2b4..ec7f5cd7a 100644 ---- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp -+++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp -@@ -60,38 +60,40 @@ using namespace ::Microsoft::Terminal::Settings::Model; - - namespace winrt::Microsoft::Terminal::Settings::Model::implementation - { -+ ApplicationState::ApplicationState(std::filesystem::path path) noexcept : -+ BaseApplicationState{ path } {} -+ - // Returns the application-global ApplicationState object. - Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance() - { - static auto state = winrt::make_self(GetBaseSettingsPath() / stateFileName); -+ state->Reload(); - return *state; - } - -- ApplicationState::ApplicationState(std::filesystem::path path) noexcept : -- _path{ std::move(path) }, -- _throttler{ std::chrono::seconds(1), [this]() { _write(); } } -+ void ApplicationState::FromJson(const Json::Value& root) const noexcept - { -- _read(); -- } -- -- // The destructor ensures that the last write is flushed to disk before returning. -- ApplicationState::~ApplicationState() -- { -- // This will ensure that we not just cancel the last outstanding timer, -- // but instead force it to run as soon as possible and wait for it to complete. -- _throttler.flush(); -+ auto state = _state.lock(); -+ // GetValueForKey() comes in two variants: -+ // * take a std::optional reference -+ // * return std::optional by value -+ // At the time of writing the former version skips missing fields in the json, -+ // but we want to explicitly clear state fields that were removed from state.json. -+#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey>(root, key); -+ MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) -+#undef MTSM_APPLICATION_STATE_GEN - } -- -- // Re-read the state.json from disk. -- void ApplicationState::Reload() const noexcept -+ Json::Value ApplicationState::ToJson() const noexcept - { -- _read(); -- } -+ Json::Value root{ Json::objectValue }; - -- // Returns the state.json path on the disk. -- winrt::hstring ApplicationState::FilePath() const noexcept -- { -- return winrt::hstring{ _path.wstring() }; -+ { -+ auto state = _state.lock_shared(); -+#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name); -+ MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) -+#undef MTSM_APPLICATION_STATE_GEN -+ } -+ return root; - } - - // Generate all getter/setters -@@ -115,58 +117,4 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation - MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) - #undef MTSM_APPLICATION_STATE_GEN - -- // Deserializes the state.json at _path into this ApplicationState. -- // * ANY errors during app state will result in the creation of a new empty state. -- // * ANY errors during runtime will result in changes being partially ignored. -- void ApplicationState::_read() const noexcept -- try -- { -- const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{}); -- if (data.empty()) -- { -- return; -- } -- -- std::string errs; -- std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; -- -- Json::Value root; -- if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs)) -- { -- throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); -- } -- -- auto state = _state.lock(); -- // GetValueForKey() comes in two variants: -- // * take a std::optional reference -- // * return std::optional by value -- // At the time of writing the former version skips missing fields in the json, -- // but we want to explicitly clear state fields that were removed from state.json. --#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey>(root, key); -- MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) --#undef MTSM_APPLICATION_STATE_GEN -- } -- CATCH_LOG() -- -- // Serialized this ApplicationState (in `context`) into the state.json at _path. -- // * Errors are only logged. -- // * _state->_writeScheduled is set to false, signaling our -- // setters that _synchronize() needs to be called again. -- void ApplicationState::_write() const noexcept -- try -- { -- Json::Value root{ Json::objectValue }; -- -- { -- auto state = _state.lock_shared(); --#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name); -- MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) --#undef MTSM_APPLICATION_STATE_GEN -- } -- -- Json::StreamWriterBuilder wbuilder; -- const auto content = Json::writeString(wbuilder, root); -- WriteUTF8FileAtomic(_path, content); -- } -- CATCH_LOG() - } -diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h -index 71c6a576e..f7011289e 100644 ---- a/src/cascadia/TerminalSettingsModel/ApplicationState.h -+++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h -@@ -12,12 +12,11 @@ Abstract: - --*/ - #pragma once - -+#include "BaseApplicationState.h" - #include "ApplicationState.g.h" - #include "WindowLayout.g.h" - - #include --#include --#include - #include - - // This macro generates all getters and setters for ApplicationState. -@@ -40,18 +39,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation - friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait; - }; - -- struct ApplicationState : ApplicationStateT -+ struct ApplicationState : public BaseApplicationState, ApplicationStateT - { - static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance(); - - ApplicationState(std::filesystem::path path) noexcept; -- ~ApplicationState(); - -- // Methods -- void Reload() const noexcept; -- -- // General getters/setters -- winrt::hstring FilePath() const noexcept; -+ virtual void FromJson(const Json::Value& root) const noexcept override; -+ virtual Json::Value ToJson() const noexcept override; - - // State getters/setters - #define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \ -@@ -67,13 +62,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation - MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN) - #undef MTSM_APPLICATION_STATE_GEN - }; -- -- void _write() const noexcept; -- void _read() const noexcept; -- -- std::filesystem::path _path; - til::shared_mutex _state; -- til::throttled_func_trailing<> _throttler; - }; - } - -diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp -new file mode 100644 -index 000000000..3b5c02446 ---- /dev/null -+++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp -@@ -0,0 +1,92 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#include "pch.h" -+#include "BaseApplicationState.h" -+#include "CascadiaSettings.h" -+ -+#include "JsonUtils.h" -+#include "FileUtils.h" -+ -+constexpr std::wstring_view stateFileName{ L"state.json" }; -+using namespace ::Microsoft::Terminal::Settings::Model; -+ -+BaseApplicationState::BaseApplicationState(std::filesystem::path path) noexcept : -+ _path{ std::move(path) }, -+ _throttler{ std::chrono::seconds(1), [this]() { _write(); } } -+{ -+ // DON'T _read() here! _read() will call FromJson, which is virtual, and -+ // needs to be implemented in a derived class. Classes that derive from -+ // BaseApplicationState should make sure to call Reload() after construction -+ // to ensure the data is loaded. -+} -+ -+// The destructor ensures that the last write is flushed to disk before returning. -+BaseApplicationState::~BaseApplicationState() -+{ -+ // This will ensure that we not just cancel the last outstanding timer, -+ // but instead force it to run as soon as possible and wait for it to complete. -+ _throttler.flush(); -+} -+ -+// Re-read the state.json from disk. -+void BaseApplicationState::Reload() const noexcept -+{ -+ _read(); -+} -+ -+// Returns the state.json path on the disk. -+winrt::hstring BaseApplicationState::FilePath() const noexcept -+{ -+ return winrt::hstring{ _path.wstring() }; -+} -+ -+// Deserializes the state.json at _path into this ApplicationState. -+// * ANY errors during app state will result in the creation of a new empty state. -+// * ANY errors during runtime will result in changes being partially ignored. -+void BaseApplicationState::_read() const noexcept -+try -+{ -+ const auto data = _readFileContents().value_or(std::string{}); -+ if (data.empty()) -+ { -+ return; -+ } -+ -+ std::string errs; -+ std::unique_ptr reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() }; -+ -+ Json::Value root; -+ if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs)) -+ { -+ throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); -+ } -+ -+ FromJson(root); -+} -+CATCH_LOG() -+ -+// Serialized this ApplicationState (in `context`) into the state.json at _path. -+// * Errors are only logged. -+// * _state->_writeScheduled is set to false, signaling our -+// setters that _synchronize() needs to be called again. -+void BaseApplicationState::_write() const noexcept -+try -+{ -+ Json::Value root{ this->ToJson() }; -+ -+ Json::StreamWriterBuilder wbuilder; -+ const auto content = Json::writeString(wbuilder, root); -+ _writeFileContents(content); -+} -+CATCH_LOG() -+ -+std::optional BaseApplicationState::_readFileContents() const -+{ -+ return ReadUTF8FileIfExists(_path); -+} -+ -+void BaseApplicationState::_writeFileContents(const std::string_view content) const -+{ -+ WriteUTF8FileAtomic(_path, content); -+} -diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.h b/src/cascadia/TerminalSettingsModel/BaseApplicationState.h -new file mode 100644 -index 000000000..e684d3192 ---- /dev/null -+++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.h -@@ -0,0 +1,36 @@ -+/*++ -+Copyright (c) Microsoft Corporation -+Licensed under the MIT license. -+ -+Module Name: -+- ApplicationState.h -+ -+Abstract: -+- TODO! -+--*/ -+#pragma once -+ -+struct BaseApplicationState -+{ -+ BaseApplicationState(std::filesystem::path path) noexcept; -+ ~BaseApplicationState(); -+ -+ // Methods -+ void Reload() const noexcept; -+ -+ // General getters/setters -+ winrt::hstring FilePath() const noexcept; -+ -+ virtual void FromJson(const Json::Value& root) const noexcept = 0; -+ virtual Json::Value ToJson() const noexcept = 0; -+ -+protected: -+ virtual std::optional _readFileContents() const; -+ virtual void _writeFileContents(const std::string_view content) const; -+ -+ void _write() const noexcept; -+ void _read() const noexcept; -+ -+ std::filesystem::path _path; -+ til::throttled_func_trailing<> _throttler; -+}; -diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp -new file mode 100644 -index 000000000..f7d72207e ---- /dev/null -+++ b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp -@@ -0,0 +1,134 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+#include "pch.h" -+#include "ElevatedState.h" -+#include "CascadiaSettings.h" -+#include "ElevatedState.g.cpp" -+ -+#include "JsonUtils.h" -+#include "FileUtils.h" -+ -+#include -+ -+constexpr std::wstring_view stateFileName{ L"elevated-state.json" }; -+ -+using namespace ::Microsoft::Terminal::Settings::Model; -+ -+namespace winrt::Microsoft::Terminal::Settings::Model::implementation -+{ -+ ElevatedState::ElevatedState(std::filesystem::path path) noexcept : -+ BaseApplicationState{ path } {} -+ -+ // Returns the application-global ElevatedState object. -+ Microsoft::Terminal::Settings::Model::ElevatedState ElevatedState::SharedInstance() -+ { -+ // TODO! place in a totally different file! and path! -+ static auto state = winrt::make_self(GetBaseSettingsPath() / stateFileName); -+ state->Reload(); -+ -+ // const auto testPath{ GetBaseSettingsPath() / L"test.json" }; -+ -+ // PSID pEveryoneSID = NULL; -+ // SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_NT_AUTHORITY; -+ // BOOL success = AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID); -+ -+ // EXPLICIT_ACCESS ea[1]; -+ // ZeroMemory(&ea, 1 * sizeof(EXPLICIT_ACCESS)); -+ // ea[0].grfAccessPermissions = KEY_READ; -+ // ea[0].grfAccessMode = SET_ACCESS; -+ // ea[0].grfInheritance = NO_INHERITANCE; -+ // ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ // ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; -+ // ea[0].Trustee.ptstrName = (LPTSTR)pEveryoneSID; -+ -+ // ACL acl; -+ // PACL pAcl = &acl; -+ // DWORD dwRes = SetEntriesInAcl(1, ea, NULL, &pAcl); -+ // dwRes; -+ -+ // SECURITY_DESCRIPTOR sd; -+ // success = InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); -+ // success = SetSecurityDescriptorDacl(&sd, -+ // TRUE, // bDaclPresent flag -+ // pAcl, -+ // FALSE); -+ -+ // SECURITY_ATTRIBUTES sa; -+ // // Initialize a security attributes structure. -+ // sa.nLength = sizeof(SECURITY_ATTRIBUTES); -+ // sa.lpSecurityDescriptor = &sd; -+ // sa.bInheritHandle = FALSE; -+ // success; -+ -+ // wil::unique_hfile file{ CreateFileW(testPath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; -+ // THROW_LAST_ERROR_IF(!file); -+ -+ return *state; -+ } -+ -+ void ElevatedState::FromJson(const Json::Value& root) const noexcept -+ { -+ auto state = _state.lock(); -+ // GetValueForKey() comes in two variants: -+ // * take a std::optional reference -+ // * return std::optional by value -+ // At the time of writing the former version skips missing fields in the json, -+ // but we want to explicitly clear state fields that were removed from state.json. -+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey>(root, key); -+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -+#undef MTSM_ELEVATED_STATE_GEN -+ } -+ Json::Value ElevatedState::ToJson() const noexcept -+ { -+ Json::Value root{ Json::objectValue }; -+ -+ { -+ auto state = _state.lock_shared(); -+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name); -+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -+#undef MTSM_ELEVATED_STATE_GEN -+ } -+ return root; -+ } -+ -+ // Generate all getter/setters -+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \ -+ type ElevatedState::name() const noexcept \ -+ { \ -+ const auto state = _state.lock_shared(); \ -+ const auto& value = state->name; \ -+ return value ? *value : type{ __VA_ARGS__ }; \ -+ } \ -+ \ -+ void ElevatedState::name(const type& value) noexcept \ -+ { \ -+ { \ -+ auto state = _state.lock(); \ -+ state->name.emplace(value); \ -+ } \ -+ \ -+ _throttler(); \ -+ } -+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -+#undef MTSM_ELEVATED_STATE_GEN -+ -+ void ElevatedState::_writeFileContents(const std::string_view content) const -+ { -+ // DON'T use WriteUTF8FileAtomic, which will write to a temporary file -+ // then rename that file to the final filename. That actually lets us -+ // overwrite the elevate file's contents even when unelevated, because -+ // we're effectively deleting the original file, then renaming a -+ // different file in it's place. -+ // -+ // We're not worried about someone else doing that though, if they do -+ // that with the wrong permissions, then we'll just ignore the file and -+ // start over. -+ WriteUTF8File(_path, content, true); -+ } -+ -+ std::optional ElevatedState::_readFileContents() const -+ { -+ return ReadUTF8FileIfExists(_path, true); -+ } -+} -diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.h b/src/cascadia/TerminalSettingsModel/ElevatedState.h -new file mode 100644 -index 000000000..d0eb8381f ---- /dev/null -+++ b/src/cascadia/TerminalSettingsModel/ElevatedState.h -@@ -0,0 +1,60 @@ -+/*++ -+Copyright (c) Microsoft Corporation -+Licensed under the MIT license. -+ -+Module Name: -+- ElevatedState.h -+ -+Abstract: -+- If the CascadiaSettings class were AppData, then this class would be LocalAppData. -+ Put anything in here that you wouldn't want to be stored next to user-editable settings. -+- Modify ElevatedState.idl and MTSM_ELEVATED_STATE_FIELDS to add new fields. -+--*/ -+#pragma once -+ -+#include "BaseApplicationState.h" -+#include "ElevatedState.g.h" -+#include -+ -+// This macro generates all getters and setters for ElevatedState. -+// It provides X with the following arguments: -+// (type, function name, JSON key, ...variadic construction arguments) -+#define MTSM_ELEVATED_STATE_FIELDS(X) \ -+ X(Windows::Foundation::Collections::IVector, AllowedCommandlines, "allowedCommandlines") -+ -+namespace winrt::Microsoft::Terminal::Settings::Model::implementation -+{ -+ struct ElevatedState : ElevatedStateT, public BaseApplicationState -+ { -+ static Microsoft::Terminal::Settings::Model::ElevatedState SharedInstance(); -+ -+ ElevatedState(std::filesystem::path path) noexcept; -+ -+ void FromJson(const Json::Value& root) const noexcept override; -+ Json::Value ToJson() const noexcept override; -+ -+ // State getters/setters -+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \ -+ type name() const noexcept; \ -+ void name(const type& value) noexcept; -+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -+#undef MTSM_ELEVATED_STATE_GEN -+ -+ private: -+ struct state_t -+ { -+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) std::optional name{ __VA_ARGS__ }; -+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) -+#undef MTSM_ELEVATED_STATE_GEN -+ }; -+ til::shared_mutex _state; -+ -+ virtual std::optional _readFileContents() const override; -+ virtual void _writeFileContents(const std::string_view content) const override; -+ }; -+} -+ -+namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation -+{ -+ BASIC_FACTORY(ElevatedState); -+} -diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.idl b/src/cascadia/TerminalSettingsModel/ElevatedState.idl -new file mode 100644 -index 000000000..09e2d05b0 ---- /dev/null -+++ b/src/cascadia/TerminalSettingsModel/ElevatedState.idl -@@ -0,0 +1,15 @@ -+// Copyright (c) Microsoft Corporation. -+// Licensed under the MIT license. -+ -+namespace Microsoft.Terminal.Settings.Model -+{ -+ [default_interface] runtimeclass ElevatedState { -+ static ElevatedState SharedInstance(); -+ -+ void Reload(); -+ -+ String FilePath { get; }; -+ -+ Windows.Foundation.Collections.IVector AllowedCommandlines { get; set; }; -+ } -+} -diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.cpp b/src/cascadia/TerminalSettingsModel/FileUtils.cpp -index 81fbb6cee..dff35897a 100644 ---- a/src/cascadia/TerminalSettingsModel/FileUtils.cpp -+++ b/src/cascadia/TerminalSettingsModel/FileUtils.cpp -@@ -8,6 +8,8 @@ - #include - #include - -+#include -+ - static constexpr std::string_view Utf8Bom{ u8"\uFEFF" }; - static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" }; - -@@ -39,10 +41,89 @@ namespace Microsoft::Terminal::Settings::Model - return baseSettingsPath; - } - -+ static bool _hasExpectedPermissions(const std::filesystem::path& path) -+ { -+ // If we want to only open the file if it's elevated, check the -+ // permissions on this file. We want to make sure that: -+ // * Everyone has permission to read -+ // * admins can do anything -+ // * no one else can do anything. -+ PACL pAcl{ nullptr }; // This doesn't need to be cleanup up apparently -+ -+ auto status = GetNamedSecurityInfo(path.c_str(), -+ SE_FILE_OBJECT, -+ DACL_SECURITY_INFORMATION, -+ nullptr, -+ nullptr, -+ &pAcl, -+ nullptr, -+ nullptr); -+ THROW_IF_WIN32_ERROR(status); -+ -+ PEXPLICIT_ACCESS pEA{ nullptr }; -+ DWORD count = 0; -+ status = GetExplicitEntriesFromAcl(pAcl, &count, &pEA); -+ THROW_IF_WIN32_ERROR(status); -+ -+ auto explicitAccessCleanup = wil::scope_exit([&]() { ::LocalFree(pEA); }); -+ -+ if (count != 2) -+ { -+ return false; -+ } -+ -+ // Now, get the Everyone and Admins SIDS so we can make sure they're -+ // the ones in this file. -+ -+ wil::unique_sid everyoneSid{}; -+ wil::unique_sid adminGroupSid{}; -+ SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; -+ SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; -+ -+ // Create a SID for the BUILTIN\Administrators group. -+ THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid)); -+ -+ // Create a well-known SID for the Everyone group. -+ THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid)); -+ -+ bool hadExpectedPermissions = true; -+ -+ // Check that the permissions are what we'd expect them to be if only -+ // admins can write to the file. This is basically a mirror of what we -+ // set up in `WriteUTF8File`. -+ -+ // For grfAccessPermissions, GENERIC_ALL turns into STANDARD_RIGHTS_ALL, -+ // and GENERIC_READ -> READ_CONTROL -+ hadExpectedPermissions = hadExpectedPermissions && WI_AreAllFlagsSet(pEA[0].grfAccessPermissions, STANDARD_RIGHTS_ALL); -+ hadExpectedPermissions = hadExpectedPermissions && pEA[0].grfInheritance == NO_INHERITANCE; -+ hadExpectedPermissions = hadExpectedPermissions && pEA[0].Trustee.TrusteeForm == TRUSTEE_IS_SID; -+ // SIDs are void*'s that happen to convert to a wchar_t -+ hadExpectedPermissions = hadExpectedPermissions && *(pEA[0].Trustee.ptstrName) == *(LPWSTR)(adminGroupSid.get()); -+ -+ // Now check the other EXPLICIT_ACCESS -+ hadExpectedPermissions = hadExpectedPermissions && WI_IsFlagSet(pEA[1].grfAccessPermissions, READ_CONTROL); -+ hadExpectedPermissions = hadExpectedPermissions && pEA[1].grfInheritance == NO_INHERITANCE; -+ hadExpectedPermissions = hadExpectedPermissions && pEA[1].Trustee.TrusteeForm == TRUSTEE_IS_SID; -+ hadExpectedPermissions = hadExpectedPermissions && *(pEA[1].Trustee.ptstrName) == *(LPWSTR)(everyoneSid.get()); -+ -+ return hadExpectedPermissions; -+ } - // Tries to read a file somewhat atomically without locking it. - // Strips the UTF8 BOM if it exists. -- std::string ReadUTF8File(const std::filesystem::path& path) -+ std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly) - { -+ if (elevatedOnly) -+ { -+ const bool hadExpectedPermissions{ _hasExpectedPermissions(path) }; -+ if (!hadExpectedPermissions) -+ { -+ // delete the file. It's been compromised. -+ LOG_LAST_ERROR_IF(!DeleteFile(path.c_str())); -+ // Exit early, because obviously there's nothing to read from the deleted file. -+ return ""; -+ } -+ } -+ - // From some casual observations we can determine that: - // * ReadFile() always returns the requested amount of data (unless the file is smaller) - // * It's unlikely that the file was changed between GetFileSize() and ReadFile() -@@ -89,11 +170,11 @@ namespace Microsoft::Terminal::Settings::Model - } - - // Same as ReadUTF8File, but returns an empty optional, if the file couldn't be opened. -- std::optional ReadUTF8FileIfExists(const std::filesystem::path& path) -+ std::optional ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly) - { - try - { -- return { ReadUTF8File(path) }; -+ return { ReadUTF8File(path, elevatedOnly) }; - } - catch (const wil::ResultException& exception) - { -@@ -106,9 +187,75 @@ namespace Microsoft::Terminal::Settings::Model - } - } - -- void WriteUTF8File(const std::filesystem::path& path, const std::string_view content) -+ void WriteUTF8File(const std::filesystem::path& path, -+ const std::string_view content, -+ const bool elevatedOnly) - { -- wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; -+ SECURITY_ATTRIBUTES sa; -+ if (elevatedOnly) -+ { -+ // This is very vaguely taken from -+ // https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c-- -+ // With using https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids -+ // to find out that -+ // * SECURITY_NT_AUTHORITY+SECURITY_LOCAL_SYSTEM_RID == NT AUTHORITY\SYSTEM -+ // * SECURITY_NT_AUTHORITY+SECURITY_BUILTIN_DOMAIN_RID+DOMAIN_ALIAS_RID_ADMINS == BUILTIN\Administrators -+ // * SECURITY_WORLD_SID_AUTHORITY+SECURITY_WORLD_RID == Everyone -+ // -+ // Raymond Chen recommended that I make this file only writable by -+ // SYSTEM, but if I did that, then even we can't write the file -+ // while elevated, which isn't what we want. -+ -+ wil::unique_sid everyoneSid{}; -+ wil::unique_sid adminGroupSid{}; -+ SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; -+ SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; -+ -+ // Create a SID for the BUILTIN\Administrators group. -+ THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid)); -+ -+ // Create a well-known SID for the Everyone group. -+ THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid)); -+ -+ EXPLICIT_ACCESS ea[2]; -+ ZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS)); -+ // Grant Admins all permissions on this file -+ ea[0].grfAccessPermissions = GENERIC_ALL; -+ ea[0].grfAccessMode = SET_ACCESS; -+ ea[0].grfInheritance = NO_INHERITANCE; -+ ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; -+ ea[0].Trustee.ptstrName = (LPWSTR)(adminGroupSid.get()); -+ -+ // Grant Everyone the permission or read this file -+ ea[1].grfAccessPermissions = GENERIC_READ; -+ ea[1].grfAccessMode = SET_ACCESS; -+ ea[1].grfInheritance = NO_INHERITANCE; -+ ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; -+ ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; -+ ea[1].Trustee.ptstrName = (LPWSTR)(everyoneSid.get()); -+ -+ ACL acl; -+ PACL pAcl = &acl; -+ THROW_IF_WIN32_ERROR(SetEntriesInAcl(2, ea, nullptr, &pAcl)); -+ -+ SECURITY_DESCRIPTOR sd; -+ THROW_IF_WIN32_BOOL_FALSE(InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)); -+ THROW_IF_WIN32_BOOL_FALSE(SetSecurityDescriptorDacl(&sd, true, pAcl, false)); -+ -+ // Initialize a security attributes structure. -+ sa.nLength = sizeof(SECURITY_ATTRIBUTES); -+ sa.lpSecurityDescriptor = &sd; -+ sa.bInheritHandle = false; -+ } -+ -+ wil::unique_hfile file{ CreateFileW(path.c_str(), -+ GENERIC_WRITE, -+ FILE_SHARE_READ | FILE_SHARE_WRITE, -+ elevatedOnly ? &sa : nullptr, -+ CREATE_ALWAYS, -+ FILE_ATTRIBUTE_NORMAL, -+ nullptr) }; - THROW_LAST_ERROR_IF(!file); - - const auto fileSize = gsl::narrow(content.size()); -@@ -121,7 +268,8 @@ namespace Microsoft::Terminal::Settings::Model - } - } - -- void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content) -+ void WriteUTF8FileAtomic(const std::filesystem::path& path, -+ const std::string_view content) - { - // GH#10787: rename() will replace symbolic links themselves and not the path they point at. - // It's thus important that we first resolve them before generating temporary path. -diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.h b/src/cascadia/TerminalSettingsModel/FileUtils.h -index d2e2eb53c..0426b4dd5 100644 ---- a/src/cascadia/TerminalSettingsModel/FileUtils.h -+++ b/src/cascadia/TerminalSettingsModel/FileUtils.h -@@ -4,8 +4,8 @@ - namespace Microsoft::Terminal::Settings::Model - { - std::filesystem::path GetBaseSettingsPath(); -- std::string ReadUTF8File(const std::filesystem::path& path); -- std::optional ReadUTF8FileIfExists(const std::filesystem::path& path); -- void WriteUTF8File(const std::filesystem::path& path, const std::string_view content); -+ std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly = false); -+ std::optional ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly = false); -+ void WriteUTF8File(const std::filesystem::path& path, const std::string_view content, const bool elevatedOnly = false); - void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content); - } -diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h -index 58124d84b..1b26f7bae 100644 ---- a/src/cascadia/TerminalSettingsModel/JsonUtils.h -+++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h -@@ -382,7 +382,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils - }; - - template -- - struct ConversionTrait> - { - std::unordered_map FromJson(const Json::Value& json) const -diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj -index 202f07808..ce12741d1 100644 ---- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj -+++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj -@@ -21,6 +21,7 @@ - - IconPathConverter.idl - -+ - - - ActionArgs.idl -@@ -35,6 +36,9 @@ - - ApplicationState.idl - -+ -+ ElevatedState.idl -+ - - CascadiaSettings.idl - -@@ -88,6 +92,7 @@ - IconPathConverter.idl - - -+ - - Create - -@@ -107,6 +112,9 @@ - - ApplicationState.idl - -+ -+ ElevatedState.idl -+ - - CascadiaSettings.idl - -@@ -156,6 +164,7 @@ - - - -+ - - - -diff --git a/src/cascadia/TerminalSettingsModel/pch.h b/src/cascadia/TerminalSettingsModel/pch.h -index d5473939c..25773a45d 100644 ---- a/src/cascadia/TerminalSettingsModel/pch.h -+++ b/src/cascadia/TerminalSettingsModel/pch.h -@@ -53,3 +53,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider); - - // Manually include til after we include Windows.Foundation to give it winrt superpowers - #include "til.h" -+ -+#include -+#include