diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp index 37382933c..b1a533ca7 100644 --- a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp @@ -47,7 +47,7 @@ winrt::hstring BaseApplicationState::FilePath() const noexcept void BaseApplicationState::_read() const noexcept try { - const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{}); + const auto data = _readFileContents().value_or(std::string{}); if (data.empty()) { return; @@ -80,3 +80,13 @@ try WriteUTF8FileAtomic(_path, 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 index 1ffe24aa9..e684d3192 100644 --- a/src/cascadia/TerminalSettingsModel/BaseApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.h @@ -25,7 +25,10 @@ struct BaseApplicationState virtual Json::Value ToJson() const noexcept = 0; protected: - virtual void _write() const noexcept; + 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; diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp index 783298b12..9e3056909 100644 --- a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp +++ b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp @@ -113,20 +113,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN) #undef MTSM_ELEVATED_STATE_GEN - // 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 ElevatedState::_write() const noexcept - try + void ElevatedState::_writeFileContents(const std::string_view content) const { - Json::Value root{ this->ToJson() }; - - Json::StreamWriterBuilder wbuilder; - const auto content = Json::writeString(wbuilder, root); // WriteUTF8FileAtomic(_path, content, true); WriteUTF8File(_path, content, true); } - CATCH_LOG() + std::optional ElevatedState::_readFileContents() const + { + return ReadUTF8FileIfExists(_path, true); + } } diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.h b/src/cascadia/TerminalSettingsModel/ElevatedState.h index 6f28f331a..d0eb8381f 100644 --- a/src/cascadia/TerminalSettingsModel/ElevatedState.h +++ b/src/cascadia/TerminalSettingsModel/ElevatedState.h @@ -48,7 +48,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation #undef MTSM_ELEVATED_STATE_GEN }; til::shared_mutex _state; - virtual void _write() const noexcept override; + + virtual std::optional _readFileContents() const override; + virtual void _writeFileContents(const std::string_view content) const override; }; } diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.cpp b/src/cascadia/TerminalSettingsModel/FileUtils.cpp index 583a9d27f..1e5b7752e 100644 --- a/src/cascadia/TerminalSettingsModel/FileUtils.cpp +++ b/src/cascadia/TerminalSettingsModel/FileUtils.cpp @@ -43,8 +43,73 @@ namespace Microsoft::Terminal::Settings::Model // 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) + { + // 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 }; + PSECURITY_DESCRIPTOR pSD{ nullptr }; + + auto status = GetNamedSecurityInfo(path.c_str(), + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + nullptr, + nullptr, + &pAcl, + nullptr, + &pSD); + THROW_IF_WIN32_ERROR(status); + pSD; + pAcl; + + PEXPLICIT_ACCESS pEA{ nullptr }; + DWORD count = 0; + status = GetExplicitEntriesFromAcl(pAcl, &count, &pEA); + THROW_IF_WIN32_ERROR(status); + THROW_HR_IF(E_FAIL, count != 2); + + PSID pEveryoneSid = nullptr; + PSID pAdminGroupSid = nullptr; + 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, &pAdminGroupSid)); + + // 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, &pEveryoneSid)); + + THROW_HR_IF(E_FAIL, pEA[0].grfAccessPermissions != GENERIC_ALL); + THROW_HR_IF(E_FAIL, pEA[0].grfInheritance != NO_INHERITANCE); + THROW_HR_IF(E_FAIL, pEA[0].Trustee.TrusteeForm != TRUSTEE_IS_SID); + THROW_HR_IF(E_FAIL, pEA[0].Trustee.TrusteeType != TRUSTEE_IS_WELL_KNOWN_GROUP); + THROW_HR_IF(E_FAIL, pEA[0].Trustee.ptstrName != pAdminGroupSid); + + THROW_HR_IF(E_FAIL, pEA[1].grfAccessPermissions != GENERIC_READ); + THROW_HR_IF(E_FAIL, pEA[1].grfInheritance != NO_INHERITANCE); + THROW_HR_IF(E_FAIL, pEA[1].Trustee.TrusteeForm != TRUSTEE_IS_SID); + THROW_HR_IF(E_FAIL, pEA[1].Trustee.TrusteeType != TRUSTEE_IS_WELL_KNOWN_GROUP); + THROW_HR_IF(E_FAIL, pEA[1].Trustee.ptstrName != pEveryoneSid); + // // 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 = (LPTSTR)pAdminGroupSid; + // 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 = (LPTSTR)pEveryoneSid; + } + // 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() @@ -91,11 +156,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) { diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.h b/src/cascadia/TerminalSettingsModel/FileUtils.h index db733f882..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); + 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); }