security_ASLR: More robust PID calculation.

security_ASLR test wants to find the PID of a single process by name
that is also a child of a named parent process. Unfortunately, the
selection of the PPID to filter the process list is currently
somewhat arbitrary - recently we added a "use the lowest PID"
heuristic to work around the fact that there could be multiple
'init' processes.

This change instead makes the code find all candidate PPIDs, then
filter the ps output to match any PPID, and only succeed if it
finds exactly one PID/PPID pair that match the names.

BUG=chromium:741110
TEST=test_that security_ASLR # passes with multiple 'init' processes
TEST=test_that security_ASLR # catches missing pid with \
                               update_engine stopped.

Change-Id: I8185248f2dc1b80f706f1f97c11a0ff7a4fd35fd
Reviewed-on: https://chromium-review.googlesource.com/568905
Commit-Ready: Josh Horwich <jhorwich@chromium.org>
Tested-by: Josh Horwich <jhorwich@chromium.org>
Reviewed-by: Luis Hector Chavez <lhchavez@chromium.org>
Reviewed-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
diff --git a/client/site_tests/security_ASLR/security_ASLR.py b/client/site_tests/security_ASLR/security_ASLR.py
index d61cc43..372c04d 100644
--- a/client/site_tests/security_ASLR/security_ASLR.py
+++ b/client/site_tests/security_ASLR/security_ASLR.py
@@ -20,12 +20,11 @@
 import pprint
 import re
 
-def _pidof(exe_name):
-    """Returns the PID of the first process with the given name."""
+def _pidsof(exe_name):
+    """Returns the PIDs of processes with the given name as a list."""
     output = utils.system_output('pidof %s' % exe_name,
                                  ignore_status=True).strip()
-    pids = [int(pid) for pid in output.split()]
-    return sorted(pids)[0]
+    return [int(pid) for pid in output.split()]
 
 
 class Process(object):
@@ -60,17 +59,31 @@
         retries = 0
         ps_results = ""
         while retries < self._START_TIMEOUT:
-            ppid = _pidof(self._parent)
-            if ppid != "":
-                get_pid_command = ('ps -C %s -o pid,ppid | grep " %s$"'
-                    ' | awk \'{print $1}\'') % (self._name, ppid)
+            # Find all PIDs matching the expected parent name, then find all
+            # PIDs that have the expected process name and any of the parent
+            # PIDs. Only succeed when there is exactly one PID/PPID pairing.
+            # This is needed to handle cases where multiple processes share the
+            # expected parent name. See crbug.com/741110 for background.
+            ppids = _pidsof(self._parent)
+            if len(ppids) > 0:
+                ppid_match = ' '.join('-e " %d$"' % ppid for ppid in ppids)
+                get_pid_command = ('ps -C %s -o pid,ppid | grep %s'
+                    ' | awk \'{print $1}\'') % (self._name, ppid_match)
                 ps_results = utils.system_output(get_pid_command).strip()
+                pids = ps_results.split()
+                if len(pids) == 1:
+                    return pids[0]
+                elif len(pids) > 1:
+                    # More than one candidate process found - rather than pick
+                    # one arbitrarily, continue to wait. This is not expected -
+                    # but continuing to wait will avoid weird failures if some
+                    # time in the future there are multiple non-transient
+                    # parent/child processes with the same names.
+                    logging.debug("Found multiple processes for '%s'",
+                                  self._name)
 
-            if ps_results != "":
-                return ps_results
-
-            # The process could not be found. We then sleep, hoping the
-            # process is just slow to initially start.
+            # The process, or its parent, could not be found. We then sleep,
+            # hoping the process is just slow to initially start.
             time.sleep(self._START_POLL_INTERVAL_SECONDS)
             retries += 1