security_RunOci: Add Oci tests

Extra command line arguments can be specified and should be appended to
those provided in config.json.  This test checks that an argument gets
to the specified program.

Add a test for alt-syscall.  This requried being able to check stderr
from the run process.

The containers will be run from crosh which runs as chronos.  To better
test the actual way the code will run, run the test containers as
chronos.

BUG=none
TEST=security_RunOci

Change-Id: I0e26549789af021eb23219738121cb84d77eaab7
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/415525
Reviewed-by: Eric Caruso <ejcaruso@chromium.org>
diff --git a/client/site_tests/security_RunOci/security_RunOci.py b/client/site_tests/security_RunOci/security_RunOci.py
index d24c032..5d45a8d 100644
--- a/client/site_tests/security_RunOci/security_RunOci.py
+++ b/client/site_tests/security_RunOci/security_RunOci.py
@@ -2,10 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import contextlib
 import glob
 import json
 import logging
 import os
+import pwd
 import re
 import shutil
 import tempfile
@@ -23,8 +25,8 @@
     "process": {
         "terminal": true,
         "user": {
-            "uid": 10000,
-            "gid": 10000
+            "uid": 0,
+            "gid": 0
         },
         "args": [
             %s
@@ -40,7 +42,12 @@
     {
         "destination": "/proc",
         "type": "proc",
-        "source": "proc"
+        "source": "proc",
+        "options": [
+            "nodev",
+            "noexec",
+            "nosuid"
+        ]
     },
     {
         "destination": "/dev",
@@ -79,64 +86,72 @@
         ],
         "uidMappings": [
         {
-            "hostID": 10000,
+            "hostID": 1000,
             "containerID": 0,
-            "size": 10
+            "size": 1
         }
         ],
         "gidMappings": [
         {
-            "hostID": 10000,
+            "hostID": 1000,
             "containerID": 0,
-            "size": 10
+            "size": 1
         }
         ]
     }
 }
 '''
 
+@contextlib.contextmanager
+def bind_mounted_root(rootfs_path):
+    utils.run(['mount', '--bind', '/', rootfs_path])
+    yield
+    utils.run(['umount', '-f', rootfs_path])
+
+
 class security_RunOci(test.test):
     version = 1
 
     preserve_srcdir = True
 
-    def get_test_option(self, handle):
-        """
-        Gets the test configuration from the json file given in handle.
-        """
-        data = json.load(handle)
-        return data['run_oci_args'], data['program_argv'], data['expected_result']
-
-
-    def run_test_in_dir(self, run_oci_args, argv, expected, oci_path):
+    def run_test_in_dir(self, test_config, oci_path):
         """
         Executes the test in the given directory that points to an OCI image.
         """
-        ret = 0
-        cmd_output = utils.system_output(
-                '/usr/bin/run_oci %s %s' % (run_oci_args, oci_path),
-                retain_output=True)
-        if cmd_output != expected:
-            ret = 1
-        return ret
+        result = utils.run(
+                ['/usr/bin/run_oci'] + ['-U'] + test_config['run_oci_args'].split() +
+                [oci_path] + test_config.get('program_extra_argv', '').split(),
+                ignore_status=True, stderr_is_expected=True, verbose=True,
+                stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS)
+        expected = test_config['expected_result'].strip()
+        if result.stdout.strip() != expected:
+            logging.error('stdout mismatch %s != %s',
+                          result.stdout.strip(), expected)
+            return False
+        expected_err = test_config.get('expected_stderr', '').strip()
+        if result.stderr.strip() != expected_err:
+            logging.error('stderr mismatch %s != %s',
+                          result.stderr.strip(), expected_err)
+            return False
+        return True
 
 
-    def run_test(self, run_oci_args, argv, expected):
+    def run_test(self, test_config):
         """
         Runs one test from the src directory.  Return 0 if the test passes,
         return 1 on failure.
         """
+        chronos_uid = pwd.getpwnam('chronos').pw_uid
         td = autotemp.tempdir()
-        os.chown(td.name, 10000, 10000)
+        os.chown(td.name, chronos_uid, chronos_uid)
         with open(os.path.join(td.name, 'config.json'), 'w') as config_file:
-            config_file.write(CONFIG_JSON_TEMPLATE % argv)
+            config_file.write(CONFIG_JSON_TEMPLATE % test_config['program_argv'])
         rootfs_path = os.path.join(td.name, 'rootfs')
         os.mkdir(rootfs_path)
-        os.chown(rootfs_path, 10000, 10000)
-        utils.run(['mount', "--bind", "/", rootfs_path])
-        ret = self.run_test_in_dir(run_oci_args, argv, expected, td.name)
-        utils.run(['umount', '-f', rootfs_path])
-        return ret
+        os.chown(rootfs_path, chronos_uid, chronos_uid)
+        with bind_mounted_root(rootfs_path):
+            return self.run_test_in_dir(test_config, td.name)
+        return False
 
 
     def run_once(self):
@@ -151,8 +166,7 @@
         for p in glob.glob('%s/test-*.json' % self.srcdir):
             name = os.path.basename(p)
             logging.info('Running: %s', name)
-            run_oci_args, argv, expected = self.get_test_option(file(p))
-            if self.run_test(run_oci_args, argv, expected):
+            if not self.run_test(json.load(file(p))):
                 failed.append(name)
             ran += 1
         if ran == 0:
diff --git a/client/site_tests/security_RunOci/src/test-alt-syscall-settime.json b/client/site_tests/security_RunOci/src/test-alt-syscall-settime.json
new file mode 100644
index 0000000..3f9843c
--- /dev/null
+++ b/client/site_tests/security_RunOci/src/test-alt-syscall-settime.json
@@ -0,0 +1,6 @@
+{
+    "run_oci_args": "--cgroup_parent=chronos_containers --alt_syscall=third_party",
+    "program_argv": "\"/bin/date\", \"--set\", \"010101\"",
+    "expected_stderr": "date: cannot set date: Function not implemented",
+    "expected_result": "Mon Jan  1 00:00:00 UTC 2001"
+}
diff --git a/client/site_tests/security_RunOci/src/test-bind-mount-trailing-slash.json b/client/site_tests/security_RunOci/src/test-bind-mount-trailing-slash.json
index a241b37..6031373 100644
--- a/client/site_tests/security_RunOci/src/test-bind-mount-trailing-slash.json
+++ b/client/site_tests/security_RunOci/src/test-bind-mount-trailing-slash.json
@@ -1,5 +1,5 @@
 {
-    "run_oci_args": "--cgroup_parent=user_containers --bind_mount=/bin:/var/log/",
+    "run_oci_args": "--cgroup_parent=chronos_containers --bind_mount=/bin:/var/log/",
     "program_argv": "\"/bin/ls\", \"/var/log/bash\"",
     "expected_result": "/var/log/bash"
 }
diff --git a/client/site_tests/security_RunOci/src/test-bind-mount.json b/client/site_tests/security_RunOci/src/test-bind-mount.json
index f40466b..6b7e897 100644
--- a/client/site_tests/security_RunOci/src/test-bind-mount.json
+++ b/client/site_tests/security_RunOci/src/test-bind-mount.json
@@ -1,5 +1,5 @@
 {
-    "run_oci_args": "--cgroup_parent=user_containers --bind_mount=/bin:/var/log",
+    "run_oci_args": "--cgroup_parent=chronos_containers --bind_mount=/bin:/var/log",
     "program_argv": "\"/bin/ls\", \"/var/log/bash\"",
     "expected_result": "/var/log/bash"
 }
diff --git a/client/site_tests/security_RunOci/src/test-cmd-line-arg.json b/client/site_tests/security_RunOci/src/test-cmd-line-arg.json
new file mode 100644
index 0000000..3f178d0
--- /dev/null
+++ b/client/site_tests/security_RunOci/src/test-cmd-line-arg.json
@@ -0,0 +1,6 @@
+{
+    "run_oci_args": "--cgroup_parent=chronos_containers --bind_mount=/bin:/var/log",
+    "program_argv": "\"/bin/ls\"",
+    "program_extra_argv": "/var/log/bash",
+    "expected_result": "/var/log/bash"
+}
diff --git a/client/site_tests/security_RunOci/src/test-gid.json b/client/site_tests/security_RunOci/src/test-gid.json
index 84bfaec..afd194f 100644
--- a/client/site_tests/security_RunOci/src/test-gid.json
+++ b/client/site_tests/security_RunOci/src/test-gid.json
@@ -1,5 +1,5 @@
 {
-    "run_oci_args": "--cgroup_parent=user_containers",
+    "run_oci_args": "--cgroup_parent=chronos_containers",
     "program_argv": "\"/usr/bin/id\", \"-g\"",
     "expected_result": "0"
 }
diff --git a/client/site_tests/security_RunOci/src/test-sys-mounted.json b/client/site_tests/security_RunOci/src/test-sys-mounted.json
deleted file mode 100644
index ab7f5ee..0000000
--- a/client/site_tests/security_RunOci/src/test-sys-mounted.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "run_oci_args": "--cgroup_parent=user_containers",
-    "program_argv": "\"/bin/bash\", \"-c\", \"grep sysfs /proc/mounts > /dev/null && echo PASS\"",
-    "expected_result": "PASS"
-}
diff --git a/client/site_tests/security_RunOci/src/test-uid.json b/client/site_tests/security_RunOci/src/test-uid.json
index 212a409..078b5aa 100644
--- a/client/site_tests/security_RunOci/src/test-uid.json
+++ b/client/site_tests/security_RunOci/src/test-uid.json
@@ -1,5 +1,5 @@
 {
-    "run_oci_args": "--cgroup_parent=user_containers",
+    "run_oci_args": "--cgroup_parent=chronos_containers",
     "program_argv": "\"/usr/bin/id\", \"-u\"",
     "expected_result": "0"
 }