1# Copyright 2017 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import dbus
6import logging
7import os.path
8import pwd
9import socket
10
11from autotest_lib.client.bin import test, utils
12from autotest_lib.client.common_lib import error
13from autotest_lib.client.cros import constants, login
14
15
16class security_SessionManagerDbusEndpoints(test.test):
17    """Verifies SessionManager DBus endpoints are not exposed.
18    """
19    version = 1
20
21    _FLAGFILE = '/tmp/security_SessionManagerDbusEndpoints_regression'
22
23
24    def _set_user_environment(self, username):
25        for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'):
26            if name in os.environ:
27                os.environ[name] = username
28
29
30    def _set_user(self, username):
31        user_info = pwd.getpwnam(username)
32        os.setegid(user_info[3])
33        os.seteuid(user_info[2])
34        self._set_user_environment(username)
35
36
37    def _reset_user(self):
38        uid = os.getuid()
39        username = pwd.getpwuid(uid)[0]
40        os.seteuid(uid)
41        os.setegid(os.getgid())
42        self._set_user_environment(username)
43
44
45    def _ps(self, proc=constants.BROWSER):
46        """Grab the oldest pid for process |proc|."""
47        pscmd = 'ps -C %s -o pid --no-header | head -1' % proc
48        return utils.system_output(pscmd)
49
50
51    def run_once(self):
52        """Main test code."""
53        login.wait_for_browser()
54        passed_enable_chrome_testing = self.test_enable_chrome_testing()
55        passed_restart_job = self.test_restart_job()
56
57        if not passed_enable_chrome_testing or not passed_restart_job:
58            raise error.TestFail('SessionManager DBus endpoints can be abused, '
59                                 'see error log')
60
61
62    def test_restart_job(self):
63        """Test SessionManager.RestartJob."""
64        bus = dbus.SystemBus()
65        proxy = bus.get_object('org.chromium.SessionManager',
66                               '/org/chromium/SessionManager')
67        session_manager = dbus.Interface(proxy,
68                                         'org.chromium.SessionManagerInterface')
69
70        # Craft a malicious replacement for the target process.
71        cmd = ['touch', self._FLAGFILE]
72
73        # Try to get our malicious replacement to run via RestartJob.
74        try:
75            remote, local = socket.socketpair(socket.AF_UNIX)
76            logging.info('Calling RestartJob(<socket>, %r)', cmd)
77            session_manager.RestartJob(dbus.types.UnixFd(remote), cmd)
78            # Fails if the RestartJob call doesn't generate an error.
79            logging.error(
80                'RestartJob did not fail when passed an arbitrary command')
81            return False
82        except dbus.DBusException as e:
83            logging.info(e.get_dbus_message())
84            pass
85        except OSError as e:
86            raise error.TestError('Could not create sockets for creds: %s', e)
87        finally:
88            try:
89                local.close()
90            except OSError:
91                pass
92
93        if os.path.exists(self._FLAGFILE):
94            logging.error('RestartJob ran an arbitrary command')
95            return False
96
97        return True
98
99
100    def test_enable_chrome_testing(self):
101        """Test SessionManager.EnableChromeTesting."""
102        self._set_user('chronos')
103
104        bus = dbus.SystemBus()
105        proxy = bus.get_object('org.chromium.SessionManager',
106                               '/org/chromium/SessionManager')
107        session_manager = dbus.Interface(proxy,
108                                         'org.chromium.SessionManagerInterface')
109
110        chrome_pid = self._ps()
111
112        # Try DBus call and make sure it fails.
113        try:
114            # DBus cannot infer the type of an empty Python list.
115            # Pass an empty dbus.Array with the correct signature, taken from
116            # platform2/login_manager/dbus_bindings/org.chromium.SessionManagerInterface.xml.
117            empty_string_array = dbus.Array(signature="as")
118            path = session_manager.EnableChromeTesting(True, empty_string_array)
119        except dbus.exceptions.DBusException as dbe:
120            logging.info(dbe)
121        else:
122            logging.error('EnableChromeTesting '
123                          'succeeded when it should have failed')
124            return False
125
126        # Make sure Chrome didn't restart.
127        if chrome_pid != self._ps():
128            logging.error('Chrome restarted during test.')
129            return False
130
131        self._reset_user()
132        return True
133