Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 286 additions & 0 deletions src/AppInstallerCLITests/Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,204 @@ void RequireAdminOwner(const std::filesystem::path& directory)
REQUIRE(EqualSid(adminSID.get(), ownerSID));
}

DWORD AccessMaskFrom(ACEPermissions permissions)
{
DWORD result = 0;

if (permissions == ACEPermissions::All)
{
result |= GENERIC_ALL;
}
else
{
if (WI_IsFlagSet(permissions, ACEPermissions::Read))
{
result |= GENERIC_READ;
}

if (WI_IsFlagSet(permissions, ACEPermissions::Write))
{
result |= GENERIC_WRITE | FILE_DELETE_CHILD;
}

if (WI_IsFlagSet(permissions, ACEPermissions::Execute))
{
result |= GENERIC_EXECUTE;
}
}

return result;
}

DWORD NormalizedAccessMaskFrom(ACEPermissions permissions)
{
DWORD result = AccessMaskFrom(permissions);
GENERIC_MAPPING genericMapping
{
FILE_GENERIC_READ,
FILE_GENERIC_WRITE,
FILE_GENERIC_EXECUTE,
FILE_ALL_ACCESS,
};

MapGenericMask(&result, &genericMapping);
return result;
}

void ApplyAclForTest(const std::filesystem::path& directory, const std::optional<ACEPrincipal>& owner, const std::map<ACEPrincipal, ACEPermissions>& acl, bool protectDacl = true)
{
auto userToken = wil::get_token_information<TOKEN_USER>();
auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID);
PSID ownerSID = nullptr;

struct ACEDetails
{
ACEPrincipal Principal;
PSID SID;
TRUSTEE_TYPE TrusteeType;
};

ACEDetails aceDetails[] =
{
{ ACEPrincipal::CurrentUser, userToken->User.Sid, TRUSTEE_IS_USER },
{ ACEPrincipal::Admins, adminSID.get(), TRUSTEE_IS_WELL_KNOWN_GROUP },
{ ACEPrincipal::System, systemSID.get(), TRUSTEE_IS_USER },
};

ULONG entriesCount = 0;
std::array<EXPLICIT_ACCESS_W, ARRAYSIZE(aceDetails)> explicitAccess;

for (const auto& ace : aceDetails)
{
if (owner && owner.value() == ace.Principal)
{
ownerSID = ace.SID;
}

auto itr = acl.find(ace.Principal);
if (itr != acl.end())
{
EXPLICIT_ACCESS_W& entry = explicitAccess[entriesCount++];
entry = {};
entry.grfAccessPermissions = AccessMaskFrom(itr->second);
entry.grfAccessMode = SET_ACCESS;
entry.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
entry.Trustee.TrusteeForm = TRUSTEE_IS_SID;
entry.Trustee.TrusteeType = ace.TrusteeType;
entry.Trustee.ptstrName = reinterpret_cast<LPWCH>(ace.SID);
}
}

wil::unique_any<PACL, decltype(&::LocalFree), ::LocalFree> appliedAcl;
THROW_IF_WIN32_ERROR(SetEntriesInAclW(entriesCount, explicitAccess.data(), nullptr, &appliedAcl));

SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION;
if (protectDacl)
{
securityInformation |= PROTECTED_DACL_SECURITY_INFORMATION;
}

if (ownerSID)
{
securityInformation |= OWNER_SECURITY_INFORMATION;
}

std::wstring path = directory.wstring();
THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, securityInformation, ownerSID, nullptr, appliedAcl.get(), nullptr));
}

void AddUnexpectedUsersAce(const std::filesystem::path& directory)
{
wil::unique_hlocal_security_descriptor securityDescriptor;
PACL existingDacl = nullptr;
THROW_IF_WIN32_ERROR(GetNamedSecurityInfoW(directory.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &existingDacl, nullptr, &securityDescriptor));

auto usersSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS);
EXPLICIT_ACCESS_W entry = {};
entry.grfAccessPermissions = GENERIC_READ;
entry.grfAccessMode = GRANT_ACCESS;
entry.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
entry.Trustee.TrusteeForm = TRUSTEE_IS_SID;
entry.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
entry.Trustee.ptstrName = reinterpret_cast<LPWCH>(usersSID.get());

wil::unique_any<PACL, decltype(&::LocalFree), ::LocalFree> updatedDacl;
THROW_IF_WIN32_ERROR(SetEntriesInAclW(1, &entry, existingDacl, &updatedDacl));

std::wstring path = directory.wstring();
THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, updatedDacl.get(), nullptr));
}

void ApplyCombinedAceAclForTest(const std::filesystem::path& directory, const std::optional<ACEPrincipal>& owner, const std::map<ACEPrincipal, ACEPermissions>& acl)
{
auto userToken = wil::get_token_information<TOKEN_USER>();
auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID);
PSID ownerSID = nullptr;

struct ACEDetails
{
ACEPrincipal Principal;
PSID SID;
};

ACEDetails aceDetails[] =
{
{ ACEPrincipal::CurrentUser, userToken->User.Sid },
{ ACEPrincipal::Admins, adminSID.get() },
{ ACEPrincipal::System, systemSID.get() },
};

DWORD aclSize = sizeof(ACL);
for (const auto& ace : aceDetails)
{
if (owner && owner.value() == ace.Principal)
{
ownerSID = ace.SID;
}

if (acl.count(ace.Principal) != 0)
{
aclSize += sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(ace.SID);
}
}

std::vector<BYTE> aclBuffer(aclSize);
PACL appliedAcl = reinterpret_cast<PACL>(aclBuffer.data());
THROW_IF_WIN32_BOOL_FALSE(InitializeAcl(appliedAcl, aclSize, ACL_REVISION));

for (const auto& ace : aceDetails)
{
auto itr = acl.find(ace.Principal);
if (itr != acl.end())
{
THROW_IF_WIN32_BOOL_FALSE(AddAccessAllowedAceEx(
appliedAcl,
ACL_REVISION,
CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE,
NormalizedAccessMaskFrom(itr->second),
ace.SID));
}
}

SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION;
if (ownerSID)
{
securityInformation |= OWNER_SECURITY_INFORMATION;
}

std::wstring path = directory.wstring();
THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(
&path[0],
SE_FILE_OBJECT,
securityInformation,
ownerSID,
nullptr,
appliedAcl,
nullptr));
}

TEST_CASE("ApplyACL_CurrentUserOwner", "[runtime]")
{
TempDirectory directory("CurrentUserOwner");
Expand Down Expand Up @@ -104,6 +302,94 @@ TEST_CASE("ApplyACL_CurrentUserOwner_SystemAll", "[runtime]")
REQUIRE(CanWriteToPath(directory));
}

TEST_CASE("ShouldApplyACL_FalseWhenSecurityAlreadyMatches", "[runtime]")
{
TempDirectory directory("ShouldApplyACLExactMatch");
PathDetails details;
details.Path = directory;
details.SetOwner(ACEPrincipal::CurrentUser);
details.ACL[ACEPrincipal::System] = ACEPermissions::All;
details.ACL[ACEPrincipal::Admins] = ACEPermissions::All;

details.ApplyACL();

REQUIRE_FALSE(details.ShouldApplyACL());
}

TEST_CASE("ShouldApplyACL_FalseWhenSecurityIsSemanticallyEquivalent", "[runtime]")
{
TempDirectory directory("ShouldApplyACLSemanticMatch");
PathDetails details;
details.Path = directory;
details.SetOwner(ACEPrincipal::CurrentUser);
details.ACL[ACEPrincipal::System] = ACEPermissions::All;
details.ACL[ACEPrincipal::Admins] = ACEPermissions::All;

ApplyCombinedAceAclForTest(directory, details.Owner, details.ACL);

REQUIRE_FALSE(details.ShouldApplyACL());
}

TEST_CASE("ShouldApplyACL_TrueWhenOwnerMismatched", "[runtime]")
{
TempDirectory directory("ShouldApplyACLMismatchedOwner");
PathDetails actualDetails;
actualDetails.Path = directory;
actualDetails.SetOwner(ACEPrincipal::CurrentUser);
actualDetails.ACL[ACEPrincipal::System] = ACEPermissions::All;
actualDetails.ACL[ACEPrincipal::Admins] = ACEPermissions::All;
actualDetails.ApplyACL();

PathDetails expectedDetails = actualDetails;
expectedDetails.Owner = ACEPrincipal::Admins;

REQUIRE(expectedDetails.ShouldApplyACL());
}

TEST_CASE("ShouldApplyACL_TrueWhenPermissionsMismatched", "[runtime]")
{
TempDirectory directory("ShouldApplyACLMismatchedPermissions");
PathDetails actualDetails;
actualDetails.Path = directory;
actualDetails.SetOwner(ACEPrincipal::CurrentUser);
actualDetails.ACL[ACEPrincipal::System] = ACEPermissions::All;
actualDetails.ACL[ACEPrincipal::Admins] = ACEPermissions::All;
actualDetails.ApplyACL();

PathDetails expectedDetails = actualDetails;
expectedDetails.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute;

REQUIRE(expectedDetails.ShouldApplyACL());
}

TEST_CASE("ShouldApplyACL_TrueWhenDaclIsNotProtected", "[runtime]")
{
TempDirectory directory("ShouldApplyACLUnprotectedDacl");
PathDetails details;
details.Path = directory;
details.SetOwner(ACEPrincipal::CurrentUser);
details.ACL[ACEPrincipal::System] = ACEPermissions::All;
details.ACL[ACEPrincipal::Admins] = ACEPermissions::All;

ApplyAclForTest(directory, details.Owner, details.ACL, false);

REQUIRE(details.ShouldApplyACL());
}

TEST_CASE("ShouldApplyACL_TrueWhenUnexpectedAceExists", "[runtime]")
{
TempDirectory directory("ShouldApplyACLUnexpectedAce");
PathDetails details;
details.Path = directory;
details.SetOwner(ACEPrincipal::CurrentUser);
details.ACL[ACEPrincipal::System] = ACEPermissions::All;
details.ACL[ACEPrincipal::Admins] = ACEPermissions::All;
details.ApplyACL();
AddUnexpectedUsersAce(directory);

REQUIRE(details.ShouldApplyACL());
}

TEST_CASE("VerifyDevModeEnabledCheck", "[runtime]")
{
if (!Runtime::IsRunningAsAdmin())
Expand Down
Loading