Modified chrome://sandbox to more accurately describe sandboxing

Originally the chrome://sandbox page displayed "SUID Sandbox" as red
when the SUID sandbox was off, even if the namespace sandbox was on. To
avoid indicating that anything is wrong, this combines "SUID Sandbox"
and "Namespace Sandbox" into one row that displays green for namespace,
yellow for SUID, and red for neither.

Also, when the Chrome renderers are sandboxed with user namespaces,
any process in the parent namespace with the same UID is able to
ptrace the renderer. However, the chrome://sandbox page displays Yama
LSM as enforcing. This makes it clear that Yama LSM is not protecting
the renderer processes from ptrace by adding "Ptrace Protection with
Yama LSM (Non-broker)" to the webpage.

start chrome with all three sandboxing possibilities, run
./browser_tests --gtest_filter="Sandbox*"

Bug: 870527, 870534
Test: start chrome with Yama disabled, enabled, and with SetUID sandbox,
Change-Id: I2e4735363a4dceee4947757a74451e3e102c4250
Reviewed-on: https://chromium-review.googlesource.com/1162764
Commit-Queue: Matthew Denton <mpdenton@chromium.org>
Reviewed-by: Michael Giuffrida <michaelpg@chromium.org>
Reviewed-by: Chris Palmer <palmer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#581656}
diff --git a/chrome/browser/resources/sandbox_internals/sandbox_internals.html b/chrome/browser/resources/sandbox_internals/sandbox_internals.html
index 308e08c..0531ce6 100644
--- a/chrome/browser/resources/sandbox_internals/sandbox_internals.html
+++ b/chrome/browser/resources/sandbox_internals/sandbox_internals.html
@@ -22,6 +22,9 @@
       .bad {
         background-color: rgb(249, 156, 149);
       }
+      .medium {
+        background-color: rgb(255, 255, 90);
+      }
       .info {
         background-color: rgb(169, 217, 239);
       }
diff --git a/chrome/browser/resources/sandbox_internals/sandbox_internals.js b/chrome/browser/resources/sandbox_internals/sandbox_internals.js
index 81c4ad5..f691b62 100644
--- a/chrome/browser/resources/sandbox_internals/sandbox_internals.js
+++ b/chrome/browser/resources/sandbox_internals/sandbox_internals.js
@@ -3,9 +3,16 @@
 // found in the LICENSE file.
 
 (function() {
-let GOOD = 'good';
-let BAD = 'bad';
-let INFO = 'info';
+/**
+ * CSS classes for different statuses.
+ * @enum {string}
+ */
+const StatusClass = {
+  GOOD: 'good',
+  BAD: 'bad',
+  MEDIUM: 'medium',
+  INFO: 'info'
+};
 
 /**
  * Adds a row to the sandbox status table.
@@ -39,7 +46,8 @@
  * @return {Element} The newly added TR.
  */
 function addGoodBadRow(name, result) {
-  return addStatusRow(name, result ? 'Yes' : 'No', result ? GOOD : BAD);
+  return addStatusRow(
+      name, result ? 'Yes' : 'No', result ? StatusClass.GOOD : StatusClass.BAD);
 }
 
 /**
@@ -61,19 +69,21 @@
     var isTsync = false;
     var isChromeSeccomp = false;
 
-    addStatusRow('PID', status.pid, INFO);
-    addStatusRow('UID', status.uid, INFO);
+    addStatusRow('PID', status.pid, StatusClass.INFO);
+    addStatusRow('UID', status.uid, StatusClass.INFO);
     isIsolated = status.secontext.indexOf(':isolated_app:') != -1;
-    addStatusRow('SELinux Context', status.secontext, isIsolated ? GOOD : BAD);
+    addStatusRow(
+        'SELinux Context', status.secontext,
+        isIsolated ? StatusClass.GOOD : StatusClass.BAD);
 
     let procStatus = status.procStatus.split('\n');
     for (let line of procStatus) {
       if (line.startsWith('Seccomp')) {
         var value = line.split(':')[1].trim();
-        var cssClass = BAD;
+        var cssClass = StatusClass.BAD;
         if (value == '2') {
           value = 'Yes - TSYNC (' + line + ')';
-          cssClass = GOOD;
+          cssClass = StatusClass.GOOD;
           isTsync = true;
         } else if (value == '1') {
           value = 'Yes (' + line + ')';
@@ -106,9 +116,9 @@
     }
     addStatusRow(
         'Seccomp-BPF Enabled (Chrome)', seccompStatus,
-        status.seccompStatus == 4 ? GOOD : BAD);
+        status.seccompStatus == 4 ? StatusClass.GOOD : StatusClass.BAD);
 
-    addStatusRow('Android Build ID', status.androidBuildId, INFO);
+    addStatusRow('Android Build ID', status.androidBuildId, StatusClass.INFO);
 
     setEvaluation(isIsolated && isTsync && isChromeSeccomp);
   });
@@ -118,15 +128,42 @@
  * Main page handler for desktop Linux.
  */
 function linuxHandler() {
-  addGoodBadRow('SUID Sandbox', loadTimeData.getBoolean('suid'));
-  addGoodBadRow('Namespace Sandbox', loadTimeData.getBoolean('userNs'));
+  let suidSandbox = loadTimeData.getBoolean('suid');
+  let nsSandbox = loadTimeData.getBoolean('userNs');
+
+  let layer1SandboxType = 'None';
+  let layer1SandboxCssClass = StatusClass.BAD;
+  if (suidSandbox) {
+    layer1SandboxType = 'SUID';
+    layer1SandboxCssClass = StatusClass.MEDIUM;
+  } else if (nsSandbox) {
+    layer1SandboxType = 'Namespace';
+    layer1SandboxCssClass = StatusClass.GOOD;
+  }
+
+  addStatusRow('Layer 1 Sandbox', layer1SandboxType, layer1SandboxCssClass);
   addGoodBadRow('PID namespaces', loadTimeData.getBoolean('pidNs'));
   addGoodBadRow('Network namespaces', loadTimeData.getBoolean('netNs'));
   addGoodBadRow('Seccomp-BPF sandbox', loadTimeData.getBoolean('seccompBpf'));
   addGoodBadRow(
       'Seccomp-BPF sandbox supports TSYNC',
       loadTimeData.getBoolean('seccompTsync'));
-  addGoodBadRow('Yama LSM Enforcing', loadTimeData.getBoolean('yama'));
+
+  let enforcingYamaBroker = loadTimeData.getBoolean('yamaBroker');
+  addGoodBadRow(
+      'Ptrace Protection with Yama LSM (Broker)', enforcingYamaBroker);
+
+  let enforcingYamaNonbroker = loadTimeData.getBoolean('yamaNonbroker');
+  // If there is no ptrace protection anywhere, that is bad.
+  // If there is no ptrace protection for nonbroker processes because of the
+  // user namespace sandbox, that is fine and we display as medium.
+  let yamaNonbrokerCssClass = enforcingYamaBroker ?
+      (enforcingYamaNonbroker ? StatusClass.GOOD : StatusClass.MEDIUM) :
+      StatusClass.BAD;
+  addStatusRow(
+      'Ptrace Protection with Yama LSM (Non-broker)',
+      enforcingYamaNonbroker ? 'Yes' : 'No', yamaNonbrokerCssClass);
+
   setEvaluation(loadTimeData.getBoolean('sandboxGood'));
 }
 
diff --git a/chrome/browser/ui/webui/sandbox_internals_ui.cc b/chrome/browser/ui/webui/sandbox_internals_ui.cc
index ea2ef9b..0bab7ef 100644
--- a/chrome/browser/ui/webui/sandbox_internals_ui.cc
+++ b/chrome/browser/ui/webui/sandbox_internals_ui.cc
@@ -41,7 +41,14 @@
                      status & service_manager::SandboxLinux::kSeccompBPF);
   source->AddBoolean("seccompTsync",
                      status & service_manager::SandboxLinux::kSeccompTSYNC);
-  source->AddBoolean("yama", status & service_manager::SandboxLinux::kYama);
+  source->AddBoolean("yamaBroker",
+                     status & service_manager::SandboxLinux::kYama);
+
+  // Yama does not enforce in user namespaces.
+  bool enforcing_yama_nonbroker =
+      status & service_manager::SandboxLinux::kYama &&
+      !(status & service_manager::SandboxLinux::kUserNS);
+  source->AddBoolean("yamaNonbroker", enforcing_yama_nonbroker);
 
   // Require either the setuid or namespace sandbox for our first-layer sandbox.
   bool good_layer1 = (status & service_manager::SandboxLinux::kSUID ||
diff --git a/chrome/test/data/webui/sandboxstatus_browsertest.js b/chrome/test/data/webui/sandboxstatus_browsertest.js
index f852b18..ba03d14 100644
--- a/chrome/test/data/webui/sandboxstatus_browsertest.js
+++ b/chrome/test/data/webui/sandboxstatus_browsertest.js
@@ -39,29 +39,15 @@
 TEST_F(
     'SandboxStatusUITest', 'MAYBE_testSUIDorNamespaceSandboxEnabled',
     function() {
-      var namespaceyesstring = 'Namespace Sandbox\tYes';
-      var namespacenostring = 'Namespace Sandbox\tNo';
-      var suidyesstring = 'SUID Sandbox\tYes';
-      var suidnostring = 'SUID Sandbox\tNo';
+      var sandboxnamespacestring = 'Layer 1 Sandbox\tNamespace';
+      var sandboxsuidstring = 'Layer 1 Sandbox\tSUID';
 
-      var suidyes = document.body.innerText.match(suidyesstring);
-      var suidno = document.body.innerText.match(suidnostring);
-      var namespaceyes = document.body.innerText.match(namespaceyesstring);
-      var namespaceno = document.body.innerText.match(namespacenostring);
+      var namespaceyes = document.body.innerText.match(sandboxnamespacestring);
+      var suidyes = document.body.innerText.match(sandboxsuidstring);
 
       // Exactly one of the namespace or suid sandbox should be enabled.
       expectTrue(suidyes !== null || namespaceyes !== null);
       expectFalse(suidyes !== null && namespaceyes !== null);
-
-      if (namespaceyes !== null) {
-        expectEquals(null, namespaceno);
-        expectEquals(namespaceyesstring, namespaceyes[0]);
-      }
-
-      if (suidyes !== null) {
-        expectEquals(null, suidno);
-        expectEquals(suidyesstring, suidyes[0]);
-      }
     });
 
 // The seccomp-bpf sandbox is also not compatible with ASAN.