blob: f1f580e5350032602b99f6ae54a40bd94e8cf2c4 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sandbox/win/src/sandbox_policy_diagnostic.h"
#include <stddef.h>
#include <cinttypes>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/json/json_string_value_serializer.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "sandbox/win/src/ipc_tags.h"
#include "sandbox/win/src/policy_engine_opcodes.h"
#include "sandbox/win/src/sandbox_policy_base.h"
#include "sandbox/win/src/target_process.h"
#include "sandbox/win/src/win_utils.h"
namespace sandbox {
namespace {
// Keys in base::Value snapshots of Policies for chrome://sandbox.
const char kAppContainerCapabilities[] = "appContainerCapabilities";
const char kAppContainerInitialCapabilities[] =
"appContainerInitialCapabilities";
const char kAppContainerSid[] = "appContainerSid";
const char kDesiredIntegrityLevel[] = "desiredIntegrityLevel";
const char kDesiredMitigations[] = "desiredMitigations";
const char kDisconnectCsrss[] = "disconnectCsrss";
const char kHandlesToClose[] = "handlesToClose";
const char kJobLevel[] = "jobLevel";
const char kLockdownLevel[] = "lockdownLevel";
const char kLowboxSid[] = "lowboxSid";
const char kPlatformMitigations[] = "platformMitigations";
const char kPolicyRules[] = "policyRules";
const char kProcessIds[] = "processIds";
// Values in snapshots of Policies.
const char kDisabled[] = "disabled";
const char kEnabled[] = "enabled";
base::Value ProcessIdList(std::vector<uint32_t> process_ids) {
base::Value results(base::Value::Type::LIST);
for (const auto pid : process_ids) {
results.Append(base::strict_cast<double>(pid));
}
return results;
}
std::string GetTokenLevelInEnglish(TokenLevel token) {
switch (token) {
case USER_LOCKDOWN:
return "Lockdown";
case USER_RESTRICTED:
return "Restricted";
case USER_LIMITED:
return "Limited";
case USER_INTERACTIVE:
return "Interactive";
case USER_NON_ADMIN:
return "Non Admin";
case USER_RESTRICTED_SAME_ACCESS:
return "Restricted Same Access";
case USER_UNPROTECTED:
return "Unprotected";
case USER_RESTRICTED_NON_ADMIN:
return "Restricted Non Admin";
case USER_LAST:
DCHECK(false) << "Unknown TokenType";
return "Unknown";
}
}
std::string GetJobLevelInEnglish(JobLevel job) {
switch (job) {
case JOB_LOCKDOWN:
return "Lockdown";
case JOB_RESTRICTED:
return "Restricted";
case JOB_LIMITED_USER:
return "Limited User";
case JOB_INTERACTIVE:
return "Interactive";
case JOB_UNPROTECTED:
return "Unprotected";
case JOB_NONE:
return "None";
}
}
std::string GetIntegrityLevelInEnglish(IntegrityLevel integrity) {
switch (integrity) {
case INTEGRITY_LEVEL_SYSTEM:
return "S-1-16-16384 System";
case INTEGRITY_LEVEL_HIGH:
return "S-1-16-12288 High";
case INTEGRITY_LEVEL_MEDIUM:
return "S-1-16-8192 Medium";
case INTEGRITY_LEVEL_MEDIUM_LOW:
return "S-1-16-6144 Medium Low";
case INTEGRITY_LEVEL_LOW:
return "S-1-16-4096 Low";
case INTEGRITY_LEVEL_BELOW_LOW:
return "S-1-16-2048 Below Low";
case INTEGRITY_LEVEL_UNTRUSTED:
return "S-1-16-0 Untrusted";
case INTEGRITY_LEVEL_LAST:
return "Default";
}
}
std::wstring GetSidAsString(const Sid* sid) {
std::wstring result;
if (!sid->ToSddlString(&result))
DCHECK(false) << "Failed to make sddl string";
return result;
}
std::string GetMitigationsAsHex(MitigationFlags mitigations) {
return base::StringPrintf("%016" PRIx64,
base::checked_cast<uint64_t>(mitigations));
}
std::string GetPlatformMitigationsAsHex(MitigationFlags mitigations) {
DWORD64 platform_flags[2] = {0};
size_t flags_size = 0;
sandbox::ConvertProcessMitigationsToPolicy(mitigations, &(platform_flags[0]),
&flags_size);
DCHECK(flags_size / sizeof(DWORD64) <= 2)
<< "Unexpected mitigation flags size";
if (flags_size == 2 * sizeof(DWORD64))
return base::StringPrintf("%016" PRIx64 "%016" PRIx64, platform_flags[0],
platform_flags[1]);
return base::StringPrintf("%016" PRIx64, platform_flags[0]);
}
std::string GetIpcTagAsString(IpcTag service) {
switch (service) {
case IpcTag::UNUSED:
DCHECK(false) << "Unused IpcTag";
return "Unused";
case IpcTag::PING1:
return "Ping1";
case IpcTag::PING2:
return "Ping2";
case IpcTag::NTCREATEFILE:
return "NtCreateFile";
case IpcTag::NTOPENFILE:
return "NtOpenFile";
case IpcTag::NTQUERYATTRIBUTESFILE:
return "NtQueryAttributesFile";
case IpcTag::NTQUERYFULLATTRIBUTESFILE:
return "NtQueryFullAttributesFile";
case IpcTag::NTSETINFO_RENAME:
return "NtSetInfoRename";
case IpcTag::CREATENAMEDPIPEW:
return "CreateNamedPipeW";
case IpcTag::NTOPENTHREAD:
return "NtOpenThread";
case IpcTag::NTOPENPROCESS:
return "NtOpenProcess";
case IpcTag::NTOPENPROCESSTOKEN:
return "NtOpenProcessToken";
case IpcTag::NTOPENPROCESSTOKENEX:
return "NtOpenProcessTokenEx";
case IpcTag::CREATEPROCESSW:
return "CreateProcessW";
case IpcTag::CREATEEVENT:
return "CreateEvent";
case IpcTag::OPENEVENT:
return "OpenEvent";
case IpcTag::NTCREATEKEY:
return "NtCreateKey";
case IpcTag::NTOPENKEY:
return "NtOpenKey";
case IpcTag::GDI_GDIDLLINITIALIZE:
return "GdiDllInitialize";
case IpcTag::GDI_GETSTOCKOBJECT:
return "GetStockObject";
case IpcTag::USER_REGISTERCLASSW:
return "RegisterClassW";
case IpcTag::CREATETHREAD:
return "CreateThread";
case IpcTag::NTCREATESECTION:
return "NtCreateSection";
case IpcTag::WS2SOCKET:
return "WSA_Socket";
case IpcTag::LAST:
DCHECK(false) << "Unknown IpcTag";
return "Unknown";
}
}
std::string GetOpcodeAction(EvalResult action) {
switch (action) {
case EVAL_TRUE:
return "true";
case EVAL_FALSE:
return "false";
case EVAL_ERROR:
return "error";
case ASK_BROKER:
return "askBroker";
case DENY_ACCESS:
return "deny";
case GIVE_READONLY:
return "readonly";
case GIVE_ALLACCESS:
return "allaccess";
case GIVE_CACHED:
return "cached";
case GIVE_FIRST:
return "first";
case SIGNAL_ALARM:
return "alarm";
case FAKE_SUCCESS:
return "fakeSuccess";
case FAKE_ACCESS_DENIED:
return "fakeDenied";
case TERMINATE_PROCESS:
return "terminate";
}
}
std::string GetStringMatchOperation(int pos, uint32_t options) {
if (pos == 0) {
if (options & EXACT_LENGTH)
return "exact";
else
return "prefix";
} else if (pos < 0) {
return "scan";
} else if (pos == kSeekToEnd) {
return "ends";
} else {
DCHECK(false) << "Invalid pos (" << pos << ")";
return "unknown";
}
}
std::string GetPolicyOpcode(const PolicyOpcode* opcode, bool continuation) {
// See |policy_engine_opcodes.cc|.
uint32_t args[4];
auto options = opcode->GetOptions();
auto param = opcode->GetParameter();
std::string condition;
if (options & kPolNegateEval)
condition += "!(";
switch (opcode->GetID()) {
case OP_ALWAYS_FALSE:
condition += "false";
break;
case OP_ALWAYS_TRUE:
condition += "true";
break;
case OP_NUMBER_MATCH:
opcode->GetArgument(1, &args[1]);
if (args[1] == UINT32_TYPE) {
opcode->GetArgument(0, &args[0]);
condition += base::StringPrintf("p[%d] == %x", param, args[0]);
} else {
const void* match_ptr = nullptr;
opcode->GetArgument(0, &match_ptr);
condition += base::StringPrintf("p[%d] == %p", param, match_ptr);
}
break;
case OP_NUMBER_MATCH_RANGE:
opcode->GetArgument(0, &args[0]);
opcode->GetArgument(1, &args[1]);
condition +=
base::StringPrintf("%x <= p[%d] <= %x", args[0], param, args[1]);
break;
case OP_NUMBER_AND_MATCH:
opcode->GetArgument(0, &args[0]);
condition += base::StringPrintf("p[%d] & %x", param, args[0]);
break;
case OP_WSTRING_MATCH: {
int pos;
opcode->GetArgument(1, &args[1]); // Length.
opcode->GetArgument(2, &pos); // Position.
opcode->GetArgument(3, &args[3]); // Options.
// These are not nul-terminated so we have to wrap them here.
auto match_string = std::wstring(opcode->GetRelativeString(0), 0,
static_cast<size_t>(args[1]));
condition += GetStringMatchOperation(pos, args[3]);
if (args[3] & CASE_INSENSITIVE)
condition += "_i";
condition +=
base::StringPrintf("(p[%d], '%S')", param, match_string.c_str());
} break;
case OP_ACTION:
opcode->GetArgument(0, &args[0]);
condition += GetOpcodeAction(static_cast<EvalResult>(args[0]));
break;
default:
DCHECK(false) << "Unknown Opcode";
return "Unknown";
}
if (options & kPolNegateEval)
condition += ")";
// If there is another rule add a joining token.
if (continuation) {
if (options & kPolUseOREval)
condition += " || ";
else
condition += " && ";
}
return condition;
}
// Uses |service| to index into |policy_rules| returning a list of opcodes.
base::Value GetPolicyOpcodes(const PolicyGlobal* policy_rules, IpcTag service) {
base::Value entry(base::Value::Type::LIST);
PolicyBuffer* policy_buffer =
policy_rules->entry[static_cast<size_t>(service)];
// Build up rules and emit when we hit an action.
std::string cur_rule;
for (size_t i = 0; i < policy_buffer->opcode_count; i++) {
const PolicyOpcode* opcode = &policy_buffer->opcodes[i];
if (opcode->GetID() != OP_ACTION) {
DCHECK(i + 1 < policy_buffer->opcode_count)
<< "Non-actions should not terminate rules";
bool peak = policy_buffer->opcodes[i + 1].GetID() != OP_ACTION;
cur_rule += GetPolicyOpcode(opcode, peak);
} else {
cur_rule += " -> ";
cur_rule += GetPolicyOpcode(opcode, false);
entry.Append(cur_rule);
cur_rule.clear();
}
}
return entry;
}
base::Value GetPolicyRules(const PolicyGlobal* policy_rules) {
DCHECK(policy_rules);
base::Value results(base::Value::Type::DICTIONARY);
for (size_t i = 0; i < kMaxServiceCount; i++) {
if (!policy_rules->entry[i])
continue;
IpcTag service = static_cast<IpcTag>(i);
results.SetKey(GetIpcTagAsString(service),
GetPolicyOpcodes(policy_rules, service));
}
return results;
}
// HandleMap is just wstrings, nested sets could be empty.
base::Value GetHandlesToClose(const HandleMap& handle_map) {
base::Value results(base::Value::Type::DICTIONARY);
for (const auto& kv : handle_map) {
base::Value entries(base::Value::Type::LIST);
// kv.second may be an empty map.
for (const auto& entry : kv.second) {
entries.Append(base::AsStringPiece16(entry));
}
results.SetKey(base::WideToUTF8(kv.first), std::move(entries));
}
return results;
}
} // namespace
// We are a friend of PolicyBase so that we can steal its private members
// quickly in the BrokerServices tracker thread.
PolicyDiagnostic::PolicyDiagnostic(PolicyBase* policy) {
DCHECK(policy);
// TODO(crbug/997273) Add more fields once webui plumbing is complete.
{
AutoLock lock(&policy->lock_);
for (auto&& target_process : policy->targets_) {
process_ids_.push_back(
base::strict_cast<uint32_t>(target_process->ProcessId()));
}
}
lockdown_level_ = policy->lockdown_level_;
job_level_ = policy->job_level_;
// Select the final integrity level.
if (policy->delayed_integrity_level_ == INTEGRITY_LEVEL_LAST)
desired_integrity_level_ = policy->integrity_level_;
else
desired_integrity_level_ = policy->delayed_integrity_level_;
desired_mitigations_ = policy->mitigations_ | policy->delayed_mitigations_;
if (policy->app_container_) {
app_container_sid_ =
std::make_unique<Sid>(policy->app_container_->GetPackageSid());
for (const auto& sid : policy->app_container_->GetCapabilities()) {
capabilities_.push_back(sid);
}
for (const auto& sid :
policy->app_container_->GetImpersonationCapabilities()) {
initial_capabilities_.push_back(sid);
}
app_container_type_ = policy->app_container_->GetAppContainerType();
}
if (policy->policy_) {
size_t policy_mem_size = policy->policy_->data_size + sizeof(PolicyGlobal);
policy_rules_.reset(
static_cast<sandbox::PolicyGlobal*>(::operator new(policy_mem_size)));
memcpy(policy_rules_.get(), policy->policy_, policy_mem_size);
// Fixup pointers (see |PolicyGlobal| in policy_low_level.h).
PolicyBuffer** original_entries = policy->policy_->entry;
PolicyBuffer** copy_base = policy_rules_->entry;
for (size_t i = 0; i < kMaxServiceCount; i++) {
if (policy_rules_->entry[i]) {
policy_rules_->entry[i] = reinterpret_cast<PolicyBuffer*>(
reinterpret_cast<char*>(copy_base) +
(reinterpret_cast<char*>(original_entries[i]) -
reinterpret_cast<char*>(original_entries)));
}
}
}
is_csrss_connected_ = policy->is_csrss_connected_;
handles_to_close_.insert(policy->handle_closer_.handles_to_close_.begin(),
policy->handle_closer_.handles_to_close_.end());
}
PolicyDiagnostic::~PolicyDiagnostic() = default;
const char* PolicyDiagnostic::JsonString() {
// Lazily constructs json_string_.
if (json_string_)
return json_string_->c_str();
base::Value value(base::Value::Type::DICTIONARY);
value.SetKey(kProcessIds, ProcessIdList(process_ids_));
value.SetKey(kLockdownLevel,
base::Value(GetTokenLevelInEnglish(lockdown_level_)));
value.SetKey(kJobLevel, base::Value(GetJobLevelInEnglish(job_level_)));
value.SetKey(
kDesiredIntegrityLevel,
base::Value(GetIntegrityLevelInEnglish(desired_integrity_level_)));
value.SetKey(kDesiredMitigations,
base::Value(GetMitigationsAsHex(desired_mitigations_)));
value.SetKey(kPlatformMitigations,
base::Value(GetPlatformMitigationsAsHex(desired_mitigations_)));
if (app_container_sid_) {
value.SetStringKey(
kAppContainerSid,
base::AsStringPiece16(GetSidAsString(app_container_sid_.get())));
std::vector<base::Value> caps;
for (auto sid : capabilities_) {
auto sid_value = base::Value(base::AsStringPiece16(GetSidAsString(&sid)));
caps.push_back(std::move(sid_value));
}
if (!caps.empty()) {
value.SetKey(kAppContainerCapabilities, base::Value(std::move(caps)));
}
std::vector<base::Value> imp_caps;
for (auto sid : initial_capabilities_) {
auto sid_value = base::Value(base::AsStringPiece16(GetSidAsString(&sid)));
imp_caps.push_back(std::move(sid_value));
}
if (!imp_caps.empty()) {
value.SetKey(kAppContainerInitialCapabilities,
base::Value(std::move(imp_caps)));
}
if (app_container_type_ == AppContainerType::kLowbox)
value.SetStringKey(
kLowboxSid,
base::AsStringPiece16(GetSidAsString(app_container_sid_.get())));
}
if (policy_rules_)
value.SetKey(kPolicyRules, GetPolicyRules(policy_rules_.get()));
value.SetStringKey(kDisconnectCsrss,
is_csrss_connected_ ? kDisabled : kEnabled);
if (!handles_to_close_.empty())
value.SetKey(kHandlesToClose, GetHandlesToClose(handles_to_close_));
auto json_string = std::make_unique<std::string>();
JSONStringValueSerializer to_json(json_string.get());
CHECK(to_json.Serialize(value));
json_string_ = std::move(json_string);
return json_string_->c_str();
}
} // namespace sandbox