winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

Helpers.cpp (8140B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include <wil/resource.h>
      5 #include <wil/win32_helpers.h>
      6 #include <winrt/Windows.Security.Authorization.AppCapabilityAccess.h>
      7 #include <appmodel.h>
      8 #include <Helpers.h>
      9 #include <winget/Filesystem.h>
     10 #include <winget/Security.h>
     11 #include <AppInstallerRuntime.h>
     12 #include <AppInstallerLogging.h>
     13 
     14 using namespace std::string_literals;
     15 using namespace std::string_view_literals;
     16 
     17 namespace winrt::Microsoft::Management::Deployment::implementation
     18 {
     19     namespace
     20     {
     21         static std::optional<std::string> s_callerName;
     22         static wil::srwlock s_callerNameLock;
     23     }
     24 
     25     void SetComCallerName(std::string name)
     26     {
     27         auto lock = s_callerNameLock.lock_exclusive();
     28         s_callerName.emplace(std::move(name));
     29     }
     30 
     31     std::string GetComCallerName(std::string defaultNameIfNotSet)
     32     {
     33         auto lock = s_callerNameLock.lock_shared();
     34         return s_callerName.has_value() ? s_callerName.value() : defaultNameIfNotSet;
     35     }
     36 
     37     std::pair<HRESULT, DWORD> GetCallerProcessId()
     38     {
     39         RPC_STATUS rpcStatus = RPC_S_OK;
     40         RPC_CALL_ATTRIBUTES callAttributes = {};
     41         callAttributes.Version = RPC_CALL_ATTRIBUTES_VERSION;
     42         callAttributes.Flags = RPC_QUERY_CLIENT_PID;
     43         rpcStatus = RpcServerInqCallAttributes(nullptr, &callAttributes);
     44 
     45         if (rpcStatus == RPC_S_NO_CALL_ACTIVE ||
     46             (rpcStatus == RPC_S_OK && HandleToULong(callAttributes.ClientPID) == GetCurrentProcessId()))
     47         {
     48             // in-proc is supported now.
     49             return { S_OK, GetCurrentProcessId() };
     50         }
     51         else if (rpcStatus == RPC_S_OK)
     52         {
     53             // out-of-proc case.
     54             return { S_OK, HandleToULong(callAttributes.ClientPID) };
     55         }
     56         else
     57         {
     58             return { E_ACCESSDENIED, 0 };
     59         }
     60     }
     61 
     62     std::wstring_view GetStringForCapability(Capability capability)
     63     {
     64         switch (capability)
     65         {
     66         case Capability::PackageManagement:
     67             return L"packageManagement"sv;
     68         case Capability::PackageQuery:
     69             return L"packageQuery"sv;
     70         default:
     71             winrt::throw_hresult(E_UNEXPECTED);
     72         }
     73     }
     74 
     75     HRESULT EnsureProcessHasCapability(Capability requiredCapability, DWORD callerProcessId)
     76     {
     77         bool allowed = false;
     78 
     79         if (winrt::Windows::Foundation::Metadata::ApiInformation::IsTypePresent(winrt::name_of<winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapability>()))
     80         {
     81             // Get the caller process id and use it to check if the caller has permissions to access the feature.
     82             winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus status = winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus::DeniedBySystem;
     83 
     84             winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapability capability{ nullptr };
     85 
     86             try
     87             {
     88                 capability = winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapability::CreateWithProcessIdForUser(nullptr, GetStringForCapability(requiredCapability), callerProcessId);
     89             }
     90             catch (const winrt::hresult_invalid_argument&)
     91             {
     92             }
     93 
     94             if (capability)
     95             {
     96                 status = capability.CheckAccess();
     97 
     98                 return ((status == winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus::Allowed) ? S_OK : E_ACCESSDENIED);
     99             }
    100         }
    101 
    102         // If AppCapability is not present, require at least medium IL callers
    103         auto requiredIntegrityLevel = AppInstaller::Security::IntegrityLevel::Medium;
    104 
    105         if (callerProcessId != GetCurrentProcessId())
    106         {
    107             allowed = AppInstaller::Security::IsCOMCallerIntegrityLevelAtLeast(requiredIntegrityLevel);
    108         }
    109         else
    110         {
    111             allowed = AppInstaller::Security::IsCurrentIntegrityLevelAtLeast(requiredIntegrityLevel);
    112         }
    113 
    114         return (allowed ? S_OK : E_ACCESSDENIED);
    115     }
    116 
    117     HRESULT EnsureComCallerHasCapability(Capability requiredCapability)
    118     {
    119         auto [hr, callerProcessId] = GetCallerProcessId();
    120         RETURN_IF_FAILED(hr);
    121         hr = EnsureProcessHasCapability(requiredCapability, callerProcessId);
    122         // The Windows.Management.Deployment API has set the precedent that packageManagement is a superset of packageQuery
    123         // and packageQuery does not need to be declared separately.
    124         if (FAILED(hr) && requiredCapability == Capability::PackageQuery)
    125         {
    126             hr = EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId);
    127         }
    128         RETURN_HR(hr);
    129     }
    130 
    131     // Best effort at getting caller info. This should only be used for logging.
    132     std::wstring TryGetCallerProcessInfo(DWORD callerProcessId)
    133     {
    134         wil::unique_process_handle processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, callerProcessId));
    135         if (processHandle)
    136         {
    137             WCHAR packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH]{};
    138             UINT32 length = ARRAYSIZE(packageFamilyName);
    139             if (::GetPackageFamilyName(processHandle.get(), &length, packageFamilyName) == ERROR_SUCCESS)
    140             {
    141                 // If the package is calling into itself, fall through to the executable name
    142                 if (AppInstaller::Runtime::GetPackageFamilyName() != packageFamilyName)
    143                 {
    144                     return { packageFamilyName };
    145                 }
    146             }
    147 
    148             // if the caller doesn't have an AppUserModelID then fall back to the executable name
    149             std::filesystem::path executablePath = AppInstaller::Filesystem::GetExecutablePathForProcess(processHandle.get());
    150             if (executablePath.has_filename())
    151             {
    152                 return executablePath.filename();
    153             }
    154             else if (!executablePath.empty())
    155             {
    156                 AICLI_LOG(Fail, Error, << "Unable to get valid executable for process ID [" << callerProcessId << "]: " << executablePath);
    157             }
    158         }
    159 
    160         return {};
    161     }
    162 
    163     std::string GetCallerName()
    164     {
    165         // See if caller name is set by caller
    166         std::string callerName = GetComCallerName("");
    167 
    168         // Get process string
    169         if (callerName.empty())
    170         {
    171             try
    172             {
    173                 auto [hrGetCallerId, callerProcessId] = GetCallerProcessId();
    174                 if (SUCCEEDED(hrGetCallerId))
    175                 {
    176                     callerName = AppInstaller::Utility::ConvertToUTF8(TryGetCallerProcessInfo(callerProcessId));
    177                 }
    178             }
    179             CATCH_LOG();
    180         }
    181 
    182         if (callerName.empty())
    183         {
    184             callerName = "UnknownComCaller";
    185         }
    186 
    187         return callerName;
    188     }
    189 
    190     bool IsBackgroundProcessForPolicy()
    191     {
    192         bool isBackgroundProcessForPolicy = false;
    193         try
    194         {
    195             auto [hrGetCallerId, callerProcessId] = GetCallerProcessId();
    196             if (SUCCEEDED(hrGetCallerId) && callerProcessId != GetCurrentProcessId())
    197             {
    198                 // OutOfProc case, we check for explorer.exe
    199                 auto callerNameWide = AppInstaller::Utility::ConvertToUTF16(GetCallerName());
    200                 auto processName = AppInstaller::Utility::ConvertToUTF8(std::filesystem::path{ callerNameWide }.filename().wstring());
    201                 if (::AppInstaller::Utility::CaseInsensitiveEquals("explorer.exe", processName) ||
    202                     ::AppInstaller::Utility::CaseInsensitiveEquals("taskhostw.exe", processName))
    203                 {
    204                     isBackgroundProcessForPolicy = true;
    205                 }
    206             }
    207         }
    208         CATCH_LOG();
    209 
    210         return isBackgroundProcessForPolicy;
    211     }
    212 }