// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chrome://resources/js/assert.js';
import {sendWithPromise} from 'chrome://resources/js/cr.js';
import {getRequiredElement} from 'chrome://resources/js/util.js';
interface SandboxFeature {
name: string;
enabled: boolean;
}
interface BrowserHostProcess {
processId: number;
processType: string;
name: string;
metricsName: string;
sandboxType: string;
}
interface RendererHostProcess {
processId: number;
}
/**
* This may have additional fields displayed in the JSON output.
* See //sandbox/win/src/sandbox_constants.cc for keys in policy.
*/
interface PolicyDiagnostic {
appContainerCapabilities: string[];
appContainerSid: string;
componentFilters: string;
desiredIntegrityLevel: string;
lockdownLevel: string;
lowboxSid: string;
platformMitigations: string;
processId: number;
}
interface SandboxDiagnostics {
browser: BrowserHostProcess[];
renderer: RendererHostProcess[];
policies: PolicyDiagnostic[];
features: SandboxFeature[];
}
/**
* Represents a mitigation field from the PROCESS_CREATION_MITITAGION_POLICY*
* series in Winbase.h.
*/
abstract class MitigationField {
mitigation: string;
value: number;
mask: number;
offset: number;
/**
* mask & value must be 0<=x<=255.
* @param mitigation human name of mitigation.
* @param value value to match within mask.
* @param mask applied before matching.
* @param offset within PC section.
*/
constructor(mitigation: string, value: number, mask: number, offset: number) {
this.mitigation = mitigation;
this.value = value;
this.mask = mask;
this.offset = offset;
}
/**
* Each PC field overrides this as they know where their data is.
* @param bytes platform mitigations data.
* @return chunk containing this field or null.
*/
abstract getFieldData(bytes: Uint8Array): Uint8Array|null;
/**
* Are all the bits of this field set in the mitigations represented by
* |bytes|.
* @param bytes platform mitigations.
*/
isFieldSet(bytes: Uint8Array): boolean {
if (bytes.length !== 4 && bytes.length !== 8 && bytes.length !== 16) {
throw new Error('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 bytes platform mitigations data.
* @return chunk containing this field or null.
*/
getFieldData(bytes: Uint8Array): Uint8Array {
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 {
getFieldData(bytes: Uint8Array) {
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 {
getFieldData(bytes: Uint8Array) {
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 {
fields: MitigationField[];
constructor() {
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),
new Pc2Field('RESTRICT_CORE_SHARING', 0x1, 0x03, 52),
new Pc2Field('RESTRICT_CORE_SHARING_ALWAYS_OFF', 0x2, 0x03, 52),
new Pc2Field('RESTRICT_CORE_SHARING_RESERVED', 0x3, 0x03, 52),
new Pc2Field('FSCTL_SYSTEM_CALL_DISABLE', 0x1, 0x03, 56),
new Pc2Field('FSCTL_SYSTEM_CALL_DISABLE_ALWAYS_OFF', 0x2, 0x03, 56),
new Pc2Field('FSCTL_SYSTEM_CALL_DISABLE_RESERVED', 0x3, 0x03, 56),
];
}
/**
* @param str Hex encoded data.
* @return bytes Decoded bytes.
*/
parseHexString(str: string): Uint8Array {
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 mitigations Hex encoded process mitigation flags.
* @return Matched mitigation values.
*/
enabledMitigations(mitigations: string): string[] {
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: {[sid: string]: string} = {
'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.
*/
function mapCapabilitySid(sid: string): string {
if (WELL_KNOWN_SIDS[sid]) {
return WELL_KNOWN_SIDS[sid]!;
}
return sid;
}
/**
* Adds a row to the sandbox-status table.
*/
function addRow(args: Node[]) {
const row = document.createElement('tr');
for (const td of args) {
row.appendChild(td);
}
getRequiredElement('sandbox-status').appendChild(row);
}
/**
* Makes a <td> containing arg as textContent.
*/
function makeTextEntry(textContent: string): Node {
const col = document.createElement('td');
col.textContent = textContent;
return col;
}
/**
* Makes a <td> containing formatted component filter flags.
*/
function makeComponentFilterEntry(policy: PolicyDiagnostic): Node {
const fixed = document.createElement('div');
fixed.classList.add('mitigations');
fixed.innerText = policy.componentFilters;
const col = document.createElement('td');
col.appendChild(fixed);
return col;
}
/**
* Makes an expandable <td> containing arg as textContent.
*/
function makeExpandableEntry(mainEntry: string, expandable: Expandable): Node {
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;
}
abstract class Expandable {
expanded: boolean = false;
onClick(col: HTMLElement): boolean {
this.expanded = !this.expanded;
col.innerText = this.getText();
return this.expanded;
}
abstract getText(): string;
}
class MitigationEntryExpandable extends Expandable {
mitigations: string;
constructor(mitigations: string) {
super();
this.mitigations = mitigations;
}
override getText(): string {
if (this.expanded) {
return DECODE_MITIGATIONS.enabledMitigations(this.mitigations).join('\n');
} else {
return '';
}
}
}
class AppContainerEntryExpandable extends Expandable {
caps: string[];
constructor(caps: string[]) {
super();
this.caps = caps;
}
override getText(): string {
if (this.expanded) {
return this.caps.map(mapCapabilitySid).sort().join('\n');
} else {
return '';
}
}
}
/**
* Adds a mitigations entry that can expand to show friendly names of the
* mitigations.
*/
function makeMitigationEntry(platformMitigations: string): Node {
const expander = new MitigationEntryExpandable(platformMitigations);
return makeExpandableEntry(platformMitigations, expander);
}
/**
* Formats a lowbox sid or appcontainer configuration (policies can only
* have one or the other).
*/
function makeLowboxAcEntry(policy: PolicyDiagnostic): Node {
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 =
new AppContainerEntryExpandable(policy.appContainerCapabilities);
return makeExpandableEntry(policy.appContainerSid, expander);
}
return makeTextEntry('');
}
/**
* Adds policy information for a process to the sandbox-status table.
*/
function addRowForProcess(
pid: number, type: string, name: string, sandbox: string,
policy: PolicyDiagnostic) {
if (policy) {
// Text-only items.
const entries = [
String(pid),
type,
name,
sandbox,
policy.lockdownLevel,
policy.desiredIntegrityLevel,
].map(makeTextEntry);
entries.push(makeMitigationEntry(policy.platformMitigations));
entries.push(makeComponentFilterEntry(policy));
entries.push(makeLowboxAcEntry(policy));
addRow(entries);
} else {
addRow([String(pid), type, name, 'Not Sandboxed', '', '', '', '', ''].map(
makeTextEntry));
}
}
function onGetSandboxDiagnostics(results: SandboxDiagnostics) {
// Make it easy to look up policies.
const policies: Map<number, PolicyDiagnostic> = new Map();
for (const policy of results.policies) {
policies.set(policy.processId, policy);
}
// Titles.
addRow([
'Process',
'Type',
'Name',
'Sandbox',
'Lockdown',
'Integrity',
'Mitigations',
'Component Filter',
'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.
getRequiredElement('raw-info').textContent =
'features: ' + JSON.stringify(results.features, null, 2) + '\n' +
'policies: ' + JSON.stringify(results.policies, null, 2);
}
document.addEventListener('DOMContentLoaded', () => {
sendWithPromise('requestSandboxDiagnostics').then(onGetSandboxDiagnostics);
});