Compare commits

...

4 commits

Author SHA1 Message Date
Mike Griese 306ad30753 minor nits 2021-09-14 05:47:19 -05:00
Mike Griese 00c7647594 🤦 2021-09-14 05:45:45 -05:00
Mike Griese 97b0f06504 This is everything from dev/migrie/f/non-terminal-content-elevation-warning for specifically ElevatedState 2021-09-14 05:44:25 -05:00
Mike Griese c2fcdcbe10 THis fixes #7754 2021-09-14 05:23:15 -05:00
14 changed files with 530 additions and 106 deletions

View file

@ -61,6 +61,9 @@ Author(s):
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
#include <til/mutex.h>
#include <til/throttled_func.h>
// Common includes for most tests:
#include "../../inc/argb.h"
#include "../../inc/conattrs.hpp"

View file

@ -131,11 +131,32 @@ static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceD
// Method Description:
// - Returns whether the user is either a member of the Administrators group or
// is currently elevated.
// - This will return **FALSE** if the user has UAC disabled entirely, because
// there's no separation of power between the user and an admin in that case.
// Return Value:
// - true if the user is an administrator
static bool _isUserAdmin() noexcept
try
{
DWORD dwSize;
wil::unique_handle hToken;
TOKEN_ELEVATION_TYPE elevationType;
TOKEN_ELEVATION elevationState{ 0 };
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
GetTokenInformation(hToken.get(), TokenElevationType, &elevationType, sizeof(elevationType), &dwSize);
GetTokenInformation(hToken.get(), TokenElevation, &elevationState, sizeof(elevationState), &dwSize);
if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated)
{
// In this case, the user has UAC entirely disabled. This is sorta
// weird, we treat this like the user isn't an admin at all. There's no
// separation of powers, so the things we normally want to gate on
// "having special powers" doesn't apply.
//
// See GH#7754, GH#11096
return false;
}
SID_IDENTIFIER_AUTHORITY ntAuthority{ SECURITY_NT_AUTHORITY };
wil::unique_sid adminGroupSid{};
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
@ -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();
}

View file

@ -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<ApplicationState>(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();
auto state = _state.lock();
// GetValueForKey() comes in two variants:
// * take a std::optional<T> reference
// * return std::optional<T> 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<std::optional<type>>(root, key);
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
}
// The destructor ensures that the last write is flushed to disk before returning.
ApplicationState::~ApplicationState()
Json::Value ApplicationState::ToJson() const noexcept
{
// 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();
}
Json::Value root{ Json::objectValue };
// Re-read the state.json from disk.
void ApplicationState::Reload() const noexcept
{
_read();
}
// 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<Json::CharReader> 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<T> reference
// * return std::optional<T> 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<std::optional<type>>(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()
}

View file

@ -12,12 +12,11 @@ Abstract:
--*/
#pragma once
#include "BaseApplicationState.h"
#include "ApplicationState.g.h"
#include "WindowLayout.g.h"
#include <inc/cppwinrt_utils.h>
#include <til/mutex.h>
#include <til/throttled_func.h>
#include <JsonUtils.h>
// 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<Model::WindowLayout>;
};
struct ApplicationState : ApplicationStateT<ApplicationState>
struct ApplicationState : public BaseApplicationState, ApplicationStateT<ApplicationState>
{
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_t> _state;
til::throttled_func_trailing<> _throttler;
};
}

View file

@ -0,0 +1,97 @@
// 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
{
// Use the derived class's implementation of _readFileContents to get the
// actual contents of the file.
const auto data = _readFileContents().value_or(std::string{});
if (data.empty())
{
return;
}
std::string errs;
std::unique_ptr<Json::CharReader> 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);
// Use the derived class's implementation of _writeFileContents to write the
// file to disk.
_writeFileContents(content);
}
CATCH_LOG()
std::optional<std::string> BaseApplicationState::_readFileContents() const
{
return ReadUTF8FileIfExists(_path);
}
void BaseApplicationState::_writeFileContents(const std::string_view content) const
{
WriteUTF8FileAtomic(_path, content);
}

View file

@ -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<std::string> _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;
};

View file

@ -0,0 +1,95 @@
// 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 <aclapi.h>
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()
{
static auto state = winrt::make_self<ElevatedState>(GetBaseSettingsPath() / stateFileName);
state->Reload();
return *state;
}
void ElevatedState::FromJson(const Json::Value& root) const noexcept
{
auto state = _state.lock();
// GetValueForKey() comes in two variants:
// * take a std::optional<T> reference
// * return std::optional<T> 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<std::optional<type>>(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<std::string> ElevatedState::_readFileContents() const
{
return ReadUTF8FileIfExists(_path, true);
}
}

View file

@ -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 <inc/cppwinrt_utils.h>
// 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<hstring>, AllowedCommandlines, "allowedCommandlines")
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
struct ElevatedState : ElevatedStateT<ElevatedState>, 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<type> name{ __VA_ARGS__ };
MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
#undef MTSM_ELEVATED_STATE_GEN
};
til::shared_mutex<state_t> _state;
virtual std::optional<std::string> _readFileContents() const override;
virtual void _writeFileContents(const std::string_view content) const override;
};
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
{
BASIC_FACTORY(ElevatedState);
}

View file

@ -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<String> AllowedCommandlines { get; set; };
}
}

View file

@ -8,6 +8,8 @@
#include <shlobj.h>
#include <WtExeUtils.h>
#include <aclapi.h>
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<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path)
std::optional<std::string> 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<DWORD>(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.

View file

@ -4,8 +4,8 @@
namespace Microsoft::Terminal::Settings::Model
{
std::filesystem::path GetBaseSettingsPath();
std::string ReadUTF8File(const std::filesystem::path& path);
std::optional<std::string> 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<std::string> 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);
}

View file

@ -382,7 +382,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
};
template<typename T>
struct ConversionTrait<std::unordered_map<std::string, T>>
{
std::unordered_map<std::string, T> FromJson(const Json::Value& json) const

View file

@ -21,6 +21,7 @@
<ClInclude Include="IconPathConverter.h">
<DependentUpon>IconPathConverter.idl</DependentUpon>
</ClInclude>
<ClInclude Include="BaseApplicationState.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="ActionArgs.h">
<DependentUpon>ActionArgs.idl</DependentUpon>
@ -35,6 +36,9 @@
<ClInclude Include="ApplicationState.h">
<DependentUpon>ApplicationState.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ElevatedState.h">
<DependentUpon>ElevatedState.idl</DependentUpon>
</ClInclude>
<ClInclude Include="CascadiaSettings.h">
<DependentUpon>CascadiaSettings.idl</DependentUpon>
</ClInclude>
@ -88,6 +92,7 @@
<DependentUpon>IconPathConverter.idl</DependentUpon>
</ClCompile>
<ClCompile Include="init.cpp" />
<ClCompile Include="BaseApplicationState.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@ -107,6 +112,9 @@
<ClCompile Include="ApplicationState.cpp">
<DependentUpon>ApplicationState.idl</DependentUpon>
</ClCompile>
<ClCompile Include="ElevatedState.cpp">
<DependentUpon>ElevatedState.idl</DependentUpon>
</ClCompile>
<ClCompile Include="CascadiaSettings.cpp">
<DependentUpon>CascadiaSettings.idl</DependentUpon>
</ClCompile>
@ -156,6 +164,7 @@
<Midl Include="ActionArgs.idl" />
<Midl Include="ActionMap.idl" />
<Midl Include="ApplicationState.idl" />
<Midl Include="ElevatedState.idl" />
<Midl Include="CascadiaSettings.idl" />
<Midl Include="ColorScheme.idl" />
<Midl Include="Command.idl" />

View file

@ -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 <til/mutex.h>
#include <til/throttled_func.h>