blob: 8e77245b8604d964ba8a29b1493dc1eaadcc5f10 [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.
import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
import {$} from 'chrome://resources/js/util.m.js';
/**
* @typedef {{
* processId: number,
* processType: string,
* name: string,
* metricsName: string,
* sandboxType: string
* }}
*/
let BrowserHostProcess;
/**
* @typedef {{
* processId: number
* }}
*/
let RendererHostProcess;
/**
* This may have additional fields displayed in the JSON output.
* See //sandbox/win/src/sandbox_constants.cc for keys in policy.
* @typedef {{
* processIds: !Array<number>,
* lockdownLevel: string,
* desiredIntegrityLevel: string,
* platformMitigations: string
* }}
*/
let PolicyDiagnostic;
/**
* @typedef {{
* browser: !Array<!BrowserHostProcess>,
* renderer: !Array<!RendererHostProcess>,
* policies: !Array<!PolicyDiagnostic>
* }}
*/
let SandboxDiagnostics;
/**
* Represents a mitigation field from the PROCESS_CREATION_MITITAGION_POLICY*
* series in Winbase.h.
*/
class MitigationField {
/**
* mask & value must be 0<=x<=255.
* @param {string} mitigation human name of mitigation.
* @param {number} value value to match within mask.
* @param {number} mask applied before matching.
* @param {number} offset within PC section.
*/
constructor(mitigation, value, mask, offset) {
/** @type {string} */
this.mitigation = mitigation;
/** @type {number} */
this.value = value;
/** @type {number} */
this.mask = mask;
/** @type {number} */
this.offset = offset;
}
/**
* Each PC field overrides this as they know where their data is.
* @param {Uint8Array} bytes platform mitigations data.
* @return {Uint8Array} chunk containing this field or null.
*/
getFieldData(bytes) {
assertNotReached();
}
/**
* Are all the bits of this field set in the mitigations represented by
* |bytes|.
* @param {Uint8Array} bytes platform mitigations.
* @return {boolean}
*/
isFieldSet(bytes) {
if (bytes.length != 4 && bytes.length != 8 && bytes.length != 16) {
throw ('Platform mitigations has unexpected size');
}
const subfield = this.getFieldData(bytes);
if (subfield == null || this.offset > subfield.length * 8) {
return false;
}
const idx = subfield.length - 1 - Math.floor(this.offset / 8);
const ibit = this.offset % 8;
return (subfield[idx] & (this.mask << ibit)) == (this.value << ibit);
}
}
/**
* PROCESS_CREATION_MITIGATION_POLICY legacy bits.
*/
class PC0Field extends MitigationField {
/**
* @param {Uint8Array} bytes platform mitigations data.
* @return {Uint8Array} chunk containing this field or null.
*/
getFieldData(bytes) {
if (bytes.length == 4) {
// Win32 only 4 bytes of fields.
return bytes;
} else if (bytes.length == 8) {
return bytes;
} else {
return bytes.slice(0, 8);
}
}
}
/**
* PROCESS_CREATION_MITIGATION_POLICY_*
*/
class PC1Field extends MitigationField {
/** @override */
getFieldData(bytes) {
if (bytes.length == 8) {
return bytes;
} else if (bytes.length == 16) {
return bytes.slice(0, 8);
}
return null;
}
}
/**
* PROCESS_CREATION_MITIGATION_POLICY2_*
*/
class PC2Field extends MitigationField {
/** @override */
getFieldData(bytes) {
if (bytes.length == 8) {
return null;
} else if (bytes.length == 16) {
return bytes.slice(8, 16);
}
return null;
}
}
/**
* Helper to show enabled mitigations from a stringified hex
representation of PROCESS_CREATION_MITIGATION_POLICY_* entries.
*/
class DecodeMitigations {
constructor() {
/* @typedef {{Array<MitigationField>}} */
this.fields = [
// Defined in Windows.h from Winbase.h
// basic (pc0) mitigations in {win7},{lsb of pc1}.
new PC0Field('DEP_ENABLE', 0x1, 0x01, 0),
new PC0Field('DEP_ATL_THUNK_ENABLE', 0x2, 0x02, 0),
new PC0Field('SEHOP_ENABLE', 0x4, 0x04, 0),
// pc1 mitigations in {lsb of pc1}.
new PC1Field('FORCE_RELOCATE_IMAGES', 0x1, 0x03, 8),
new PC1Field('FORCE_RELOCATE_IMAGES_ALWAYS_OFF', 0x2, 0x03, 8),
new PC1Field('FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS', 0x3, 0x03, 8),
new PC1Field('HEAP_TERMINATE', 0x1, 0x03, 12),
new PC1Field('HEAP_TERMINATE_ALWAYS_OFF', 0x2, 0x03, 12),
new PC1Field('HEAP_TERMINATE_RESERVED', 0x3, 0x03, 12),
new PC1Field('BOTTOM_UP_ASLR', 0x1, 0x03, 16),
new PC1Field('BOTTOM_UP_ASLR_ALWAYS_OFF', 0x2, 0x03, 16),
new PC1Field('BOTTOM_UP_ASLR_RESERVED', 0x3, 0x03, 16),
new PC1Field('HIGH_ENTROPY_ASLR', 0x1, 0x03, 20),
new PC1Field('HIGH_ENTROPY_ASLR_ALWAYS_OFF', 0x2, 0x03, 20),
new PC1Field('HIGH_ENTROPY_ASLR_RESERVED', 0x3, 0x03, 20),
new PC1Field('STRICT_HANDLE_CHECKS', 0x1, 0x03, 24),
new PC1Field('STRICT_HANDLE_CHECKS_ALWAYS_OFF', 0x2, 0x03, 24),
new PC1Field('STRICT_HANDLE_CHECKS_RESERVED', 0x3, 0x03, 24),
new PC1Field('WIN32K_SYSTEM_CALL_DISABLE', 0x1, 0x03, 28),
new PC1Field('WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_OFF', 0x2, 0x03, 28),
new PC1Field('WIN32K_SYSTEM_CALL_DISABLE_RESERVED', 0x3, 0x03, 28),
new PC1Field('EXTENSION_POINT_DISABLE', 0x1, 0x03, 32),
new PC1Field('EXTENSION_POINT_DISABLE_ALWAYS_OFF', 0x2, 0x03, 32),
new PC1Field('EXTENSION_POINT_DISABLE_RESERVED', 0x3, 0x03, 32),
new PC1Field('PROHIBIT_DYNAMIC_CODE', 0x1, 0x03, 36),
new PC1Field('PROHIBIT_DYNAMIC_CODE_ALWAYS_OFF', 0x2, 0x03, 36),
new PC1Field(
'PROHIBIT_DYNAMIC_CODE_ALWAYS_ON_ALLOW_OPT_OUT', 0x3, 0x03, 36),
new PC1Field('CONTROL_FLOW_GUARD', 0x1, 0x03, 40),
new PC1Field('CONTROL_FLOW_GUARD_ALWAYS_OFF', 0x2, 0x03, 40),
new PC1Field('CONTROL_FLOW_GUARD_EXPORT_SUPPRESSION', 0x3, 0x03, 40),
new PC1Field('BLOCK_NON_MICROSOFT_BINARIES', 0x1, 0x03, 44),
new PC1Field('BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_OFF', 0x2, 0x03, 44),
new PC1Field('BLOCK_NON_MICROSOFT_BINARIES_ALLOW_STORE', 0x3, 0x03, 44),
new PC1Field('FONT_DISABLE', 0x1, 0x03, 48),
new PC1Field('FONT_DISABLE_ALWAYS_OFF', 0x2, 0x03, 48),
new PC1Field('AUDIT_NONSYSTEM_FONTS', 0x3, 0x03, 48),
new PC1Field('IMAGE_LOAD_NO_REMOTE', 0x1, 0x03, 52),
new PC1Field('IMAGE_LOAD_NO_REMOTE_ALWAYS_OFF', 0x2, 0x03, 52),
new PC1Field('IMAGE_LOAD_NO_REMOTE_RESERVED', 0x3, 0x03, 52),
new PC1Field('IMAGE_LOAD_NO_LOW_LABEL', 0x1, 0x03, 56),
new PC1Field('IMAGE_LOAD_NO_LOW_LABEL_ALWAYS_OFF', 0x2, 0x03, 56),
new PC1Field('IMAGE_LOAD_NO_LOW_LABEL_RESERVED', 0x3, 0x03, 56),
new PC1Field('IMAGE_LOAD_PREFER_SYSTEM32', 0x1, 0x03, 60),
new PC1Field('IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_OFF', 0x2, 0x03, 60),
new PC1Field('IMAGE_LOAD_PREFER_SYSTEM32_RESERVED', 0x3, 0x03, 60),
// pc2: in second 64bit block only.
new PC2Field('LOADER_INTEGRITY_CONTINUITY', 0x1, 0x03, 4),
new PC2Field('LOADER_INTEGRITY_CONTINUITY_ALWAYS_OFF', 0x2, 0x03, 4),
new PC2Field('LOADER_INTEGRITY_CONTINUITY_AUDIT', 0x3, 0x03, 4),
new PC2Field('STRICT_CONTROL_FLOW_GUARD', 0x1, 0x03, 8),
new PC2Field('STRICT_CONTROL_FLOW_GUARD_ALWAYS_OFF', 0x2, 0x03, 8),
new PC2Field('STRICT_CONTROL_FLOW_GUARD_RESERVED', 0x3, 0x03, 8),
new PC2Field('MODULE_TAMPERING_PROTECTION', 0x1, 0x03, 12),
new PC2Field('MODULE_TAMPERING_PROTECTION_ALWAYS_OFF', 0x2, 0x03, 12),
new PC2Field('MODULE_TAMPERING_PROTECTION_NOINHERIT', 0x3, 0x03, 12),
new PC2Field('RESTRICT_INDIRECT_BRANCH_PREDICTION', 0x1, 0x03, 16),
new PC2Field(
'RESTRICT_INDIRECT_BRANCH_PREDICTION_ALWAYS_OFF', 0x2, 0x03, 16),
new PC2Field(
'RESTRICT_INDIRECT_BRANCH_PREDICTION_RESERVED', 0x3, 0x03, 16),
new PC2Field('ALLOW_DOWNGRADE_DYNAMIC_CODE_POLICY', 0x1, 0x03, 20),
new PC2Field(
'ALLOW_DOWNGRADE_DYNAMIC_CODE_POLICY_ALWAYS_OFF', 0x2, 0x03, 20),
new PC2Field(
'ALLOW_DOWNGRADE_DYNAMIC_CODE_POLICY_RESERVED', 0x3, 0x03, 20),
new PC2Field('SPECULATIVE_STORE_BYPASS_DISABLE', 0x1, 0x03, 24),
new PC2Field(
'SPECULATIVE_STORE_BYPASS_DISABLE_ALWAYS_OFF', 0x2, 0x03, 24),
new PC2Field('SPECULATIVE_STORE_BYPASS_DISABLE_RESERVED', 0x3, 0x03, 24),
new PC2Field('CET_USER_SHADOW_STACKS', 0x1, 0x03, 28),
new PC2Field('CET_USER_SHADOW_STACKS_ALWAYS_OFF', 0x2, 0x03, 28),
new PC2Field('CET_USER_SHADOW_STACKS_STRICT_MODE', 0x3, 0x03, 28),
new PC2Field('USER_CET_SET_CONTEXT_IP_VALIDATION', 0x1, 0x03, 32),
new PC2Field(
'USER_CET_SET_CONTEXT_IP_VALIDATION_ALWAYS_OFF', 0x2, 0x03, 32),
new PC2Field(
'USER_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE', 0x3, 0x03, 32),
new PC2Field('BLOCK_NON_CET_BINARIES', 0x1, 0x03, 36),
new PC2Field('BLOCK_NON_CET_BINARIES_ALWAYS_OFF', 0x2, 0x03, 36),
new PC2Field('BLOCK_NON_CET_BINARIES_NON_EHCONT', 0x3, 0x03, 36),
new PC2Field('XTENDED_CONTROL_FLOW_GUARD', 0x1, 0x03, 40),
new PC2Field('XTENDED_CONTROL_FLOW_GUARD_ALWAYS_OFF', 0x2, 0x03, 40),
new PC2Field('XTENDED_CONTROL_FLOW_GUARD_RESERVED', 0x3, 0x03, 40),
new PC2Field('CET_DYNAMIC_APIS_OUT_OF_PROC_ONLY', 0x1, 0x03, 48),
new PC2Field(
'CET_DYNAMIC_APIS_OUT_OF_PROC_ONLY_ALWAYS_OFF', 0x2, 0x03, 48),
new PC2Field('CET_DYNAMIC_APIS_OUT_OF_PROC_ONLY_RESERVED', 0x3, 0x03, 48),
];
}
/**
* @param {string} str Hex encoded data.
* @return {Uint8Array} bytes Decoded bytes.
*/
parseHexString(str) {
assert((str.length % 2 == 0), 'str must have even length');
const bytes = new Uint8Array(str.length / 2);
for (let idx = 0; idx < str.length / 2; idx++) {
bytes[idx] = parseInt(str.slice(idx * 2, idx * 2 + 2), 16);
}
return bytes;
}
/**
* Return a list of platform mitigation which are set in |mitigations|.
* Mitigations will be in the same order as Winbase.h.
* @param {string} mitigations Hex encoded process mitigation flags.
* @return {!Array<string>} Matched mitigation values.
*/
enabledMitigations(mitigations) {
const bytes = this.parseHexString(mitigations);
const output = [];
for (const item of this.fields) {
if (item.isFieldSet(bytes)) {
output.push(item.mitigation);
}
}
return output;
}
}
const DECODE_MITIGATIONS = new DecodeMitigations();
const WELL_KNOWN_SIDS = {
'S-1-15-3-1': 'InternetClient',
'S-1-15-3-2': 'InternetClientServer',
'S-1-15-3-3': 'PrivateNetworkClientServer',
'S-1-15-3-4': 'PicturesLibrary',
'S-1-15-3-5': 'VideosLibrary',
'S-1-15-3-6': 'MusicLibrary',
'S-1-15-3-7': 'DocumentsLibrary',
'S-1-15-3-8': 'EnterpriseAuthentication',
'S-1-15-3-9': 'SharedUserCertificates',
'S-1-15-3-10': 'RemovableStorage',
'S-1-15-3-11': 'Appointments',
'S-1-15-3-12': 'Contacts',
'S-1-15-3-1024-3424233489-972189580-2057154623-747635277-1604371224-316187997-3786583170-1043257646':
'chromeInstallFiles',
'S-1-15-3-1024-1502825166-1963708345-2616377461-2562897074-4192028372-3968301570-1997628692-1435953622':
'lpacAppExperience',
'S-1-15-3-1024-2302894289-466761758-1166120688-1039016420-2430351297-4240214049-4028510897-3317428798':
'lpacChromeInstallFiles',
'S-1-15-3-1024-2405443489-874036122-4286035555-1823921565-1746547431-2453885448-3625952902-991631256':
'lpacCom',
'S-1-15-3-1024-3203351429-2120443784-2872670797-1918958302-2829055647-4275794519-765664414-2751773334':
'lpacCryptoServices',
'S-1-15-3-1024-126078593-3658686728-1984883306-821399696-3684079960-564038680-3414880098-3435825201':
'lpacEnterprisePolicyChangeNotifications',
'S-1-15-3-1024-1788129303-2183208577-3999474272-3147359985-1757322193-3815756386-151582180-1888101193':
'lpacIdentityServices',
'S-1-15-3-1024-3153509613-960666767-3724611135-2725662640-12138253-543910227-1950414635-4190290187':
'lpacInstrumentation',
'S-1-15-3-1024-1692970155-4054893335-185714091-3362601943-3526593181-1159816984-2199008581-497492991':
'lpacMedia',
'S-1-15-3-1024-220022770-701261984-3991292956-4208751020-2918293058-3396419331-1700932348-2078364891':
'lpacPnPNotifications',
'S-1-15-3-1024-528118966-3876874398-709513571-1907873084-3598227634-3698730060-278077788-3990600205':
'lpacServicesManagement',
'S-1-15-3-1024-1864111754-776273317-3666925027-2523908081-3792458206-3582472437-4114419977-1582884857':
'lpacSessionManagement',
'S-1-15-3-1024-1065365936-1281604716-3511738428-1654721687-432734479-3232135806-4053264122-3456934681':
'registryRead',
};
/**
* Maps capabilities to well known values.
* @param {string}
* @return {string}
*/
function mapCapabilitySid(sid) {
if (WELL_KNOWN_SIDS[sid]) {
return WELL_KNOWN_SIDS[sid];
}
return sid;
}
/**
* Adds a row to the sandbox-status table.
* @param {!Array<Node>} args
*/
function addRow(args) {
const row = document.createElement('tr');
for (const td of args) {
row.appendChild(td);
}
$('sandbox-status').appendChild(row);
}
/**
* Makes a <td> containing arg as textContent.
* @param {string} textContent
* @return {Node}
*/
function makeTextEntry(textContent) {
const col = document.createElement('td');
col.textContent = textContent;
return col;
}
/**
* Makes an expandable <td> containing arg as textContent.
* @param {string} mainEntry is always shown
* @param {Object} expandable
* @return {Node}
*/
function makeExpandableEntry(mainEntry, expandable) {
const button = document.createElement('div');
const expand = document.createElement('div');
button.innerText = '\u2795'; // (+)
button.classList.add('expander');
button.addEventListener('click', function() {
if (expandable.onClick(expand)) {
button.innerText = '\u2796'; // (-)
} else {
button.innerText = '\u2795'; // (+)
}
});
const fixed = document.createElement('div');
fixed.classList.add('mitigations');
fixed.innerText = mainEntry;
const col = document.createElement('td');
col.appendChild(button);
col.appendChild(fixed);
col.appendChild(expand);
return col;
}
/**
* Adds a mitigations entry that can expand to show friendly names of the
* mitigations.
* @param {string} platformMitigations
* @return {Node}
* @suppress {globalThis}
*/
function makeMitigationEntry(platformMitigations) {
const expander = {
expanded: false,
mitigations: platformMitigations,
onClick: function(col) {
this.expanded = !this.expanded;
col.innerText = this.getText();
return this.expanded;
},
getText: function() {
if (this.expanded) {
return DECODE_MITIGATIONS.enabledMitigations(this.mitigations)
.join('\n');
} else {
return '';
}
}
};
return makeExpandableEntry(platformMitigations, expander);
}
/**
* Formats a lowbox sid or appcontainer configuration (policies can only
* have one or the other).
* @param {PolicyDiagnostic} policy
* @return {Node}
*/
function makeLowboxAcEntry(policy) {
if (policy.lowboxSid) {
// Lowbox token does not have capabilities but should match AC entries.
const fixed = document.createElement('div');
fixed.classList.add('mitigations');
fixed.innerText = policy.lowboxSid;
const col = document.createElement('td');
col.appendChild(fixed);
return col;
}
if (policy.appContainerSid) {
// AC has identifying SID plus lockdown capabilities.
const expander = {
expanded: false,
caps: policy.appContainerCapabilities,
onClick: function(col) {
this.expanded = !this.expanded;
col.innerText = this.getText();
return this.expanded;
},
getText: function() {
if (this.expanded) {
return this.caps.map(mapCapabilitySid).sort().join('\n');
} else {
return '';
}
}
};
return makeExpandableEntry(policy.appContainerSid, expander);
}
return makeTextEntry('');
}
/**
* Adds policy information for a process to the sandbox-status table.
* @param {number} pid
* @param {string} type
* @param {string} name
* @param {string} sandbox
* @param {PolicyDiagnostic} policy
*/
function addRowForProcess(pid, type, name, sandbox, policy) {
if (policy) {
// Text-only items.
const entries = [
pid, type, name, sandbox, policy.lockdownLevel,
policy.desiredIntegrityLevel
].map(makeTextEntry);
// Clickable mitigations item.
entries.push(makeMitigationEntry(policy.platformMitigations));
entries.push(makeLowboxAcEntry(policy));
addRow(entries);
} else {
addRow(
[pid, type, name, 'Not Sandboxed', '', '', '', ''].map(makeTextEntry));
}
}
/** @param {!SandboxDiagnostics} results */
function onGetSandboxDiagnostics(results) {
// Make it easy to look up policies.
/** @type {!Map<number,!PolicyDiagnostic>} */
const policies = new Map();
for (const policy of results.policies) {
// At present only one process per TargetPolicy object.
const pid = policy.processIds[0];
policies.set(pid, policy);
}
// Titles.
addRow([
'Process', 'Type', 'Name', 'Sandbox', 'Lockdown', 'Integrity',
'Mitigations', 'Lowbox/AppContainer'
].map(makeTextEntry));
// Browser Processes.
for (const process of results.browser) {
const pid = process.processId;
const name = process.name || process.metricsName;
addRowForProcess(
pid, process.processType, name, process.sandboxType, policies.get(pid));
}
// Renderer Processes.
for (const process of results.renderer) {
const pid = process.processId;
addRowForProcess(pid, 'Renderer', '', 'Renderer', policies.get(pid));
}
// Raw Diagnostics.
$('raw-info').textContent =
'policies: ' + JSON.stringify(results.policies, null, 2);
}
document.addEventListener('DOMContentLoaded', () => {
sendWithPromise('requestSandboxDiagnostics').then(onGetSandboxDiagnostics);
});