qubesd.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #!/usr/bin/env python3.6
  2. import asyncio
  3. import functools
  4. import io
  5. import os
  6. import reprlib
  7. import signal
  8. import types
  9. import qubes
  10. import qubes.libvirtaio
  11. import qubes.utils
  12. import qubes.vm.qubesvm
  13. QUBESD_SOCK = '/var/run/qubesd.sock'
  14. class ProtocolRepr(reprlib.Repr):
  15. def repr1(self, x, level):
  16. if isinstance(x, qubes.vm.qubesvm.QubesVM):
  17. x = x.name
  18. return super().repr1(x, level)
  19. # pylint: disable=invalid-name
  20. def repr_str(self, x, level):
  21. '''Warning: this is incompatible with python 3 wrt to b'' '''
  22. return "'{}'".format(''.join(
  23. chr(c)
  24. if 0x20 < c < 0x7f and c not in (ord("'"), ord('\\'))
  25. else '\\x{:02x}'.format(c)
  26. for c in x.encode()))
  27. def repr_Label(self, x, level):
  28. return self.repr1(x.name, level)
  29. class ProtocolError(AssertionError):
  30. '''Raised when something is wrong with data received'''
  31. pass
  32. class PermissionDenied(Exception):
  33. '''Raised deliberately by handlers when we decide not to cooperate'''
  34. pass
  35. def not_in_api(func):
  36. func.not_in_api = True
  37. return func
  38. class QubesMgmt(object):
  39. def __init__(self, app, src, method, dest, arg):
  40. self.app = app
  41. self.src = self.app.domains[src.decode('ascii')]
  42. self.dest = self.app.domains[dest.decode('ascii')]
  43. self.arg = arg.decode('ascii')
  44. self.prepr = ProtocolRepr()
  45. self.method = method.decode('ascii')
  46. untrusted_func_name = self.method
  47. if untrusted_func_name.startswith('mgmt.'):
  48. untrusted_func_name = untrusted_func_name[5:]
  49. untrusted_func_name = untrusted_func_name.lower().replace('.', '_')
  50. if untrusted_func_name.startswith('_') \
  51. or not '_' in untrusted_func_name:
  52. raise ProtocolError(
  53. 'possibly malicious function name: {!r}'.format(
  54. untrusted_func_name))
  55. try:
  56. untrusted_func = getattr(self, untrusted_func_name)
  57. except AttributeError:
  58. raise ProtocolError(
  59. 'no such attribute: {!r}'.format(
  60. untrusted_func_name))
  61. if not isinstance(untrusted_func, types.MethodType):
  62. raise ProtocolError(
  63. 'no such method: {!r}'.format(
  64. untrusted_func_name))
  65. if getattr(untrusted_func, 'not_in_api', False):
  66. raise ProtocolError(
  67. 'attempt to call private method: {!r}'.format(
  68. untrusted_func_name))
  69. self.execute = untrusted_func
  70. del untrusted_func_name
  71. del untrusted_func
  72. #
  73. # PRIVATE METHODS, not to be called via RPC
  74. #
  75. @not_in_api
  76. def fire_event_for_permission(self, *args, **kwargs):
  77. return self.src.fire_event_pre('mgmt-permission:{}'.format(self.method),
  78. self.dest, self.arg, *args, **kwargs)
  79. @not_in_api
  80. def repr(self, *args, **kwargs):
  81. return self.prepr.repr(*args, **kwargs)
  82. #
  83. # ACTUAL RPC CALLS
  84. #
  85. def vm_list(self, untrusted_payload):
  86. assert self.dest.name == 'dom0'
  87. assert not self.arg
  88. assert not untrusted_payload
  89. del untrusted_payload
  90. domains = self.app.domains
  91. for selector in self.fire_event_for_permission():
  92. domains = filter(selector, domains)
  93. return ''.join('{} class={} state={}\n'.format(
  94. self.repr(vm),
  95. vm.__class__.__name__,
  96. vm.get_power_state())
  97. for vm in sorted(domains))
  98. def vm_property_get(self, untrusted_payload):
  99. assert self.arg in self.dest.property_list()
  100. assert not untrusted_payload
  101. del untrusted_payload
  102. self.fire_event_for_permission()
  103. try:
  104. value = getattr(self.dest, self.arg)
  105. except AttributeError:
  106. return 'default=True '
  107. else:
  108. return 'default={} {}'.format(
  109. str(self.dest.property_is_default(self.arg)),
  110. self.repr(value))
  111. class QubesDaemonProtocol(asyncio.Protocol):
  112. buffer_size = 65536
  113. def __init__(self, *args, app, **kwargs):
  114. super().__init__(*args, **kwargs)
  115. self.app = app
  116. self.untrusted_buffer = io.BytesIO()
  117. self.len_untrusted_buffer = 0
  118. self.transport = None
  119. def connection_made(self, transport):
  120. print('connection_made()')
  121. self.transport = transport
  122. def connection_lost(self, exc):
  123. print('connection_lost(exc={!r})'.format(exc))
  124. self.untrusted_buffer.close()
  125. def data_received(self, untrusted_data):
  126. print('data_received(untrusted_data={!r})'.format(untrusted_data))
  127. if self.len_untrusted_buffer + len(untrusted_data) > self.buffer_size:
  128. print(' request too long')
  129. self.transport.close()
  130. return
  131. self.len_untrusted_buffer += \
  132. self.untrusted_buffer.write(untrusted_data)
  133. def eof_received(self):
  134. print('eof_received()')
  135. try:
  136. src, method, dest, arg, untrusted_payload = \
  137. self.untrusted_buffer.getvalue().split(b'\0', 4)
  138. except ValueError:
  139. # TODO logging
  140. return
  141. try:
  142. mgmt = QubesMgmt(self.app, src, method, dest, arg)
  143. response = mgmt.execute(untrusted_payload=untrusted_payload)
  144. except PermissionDenied as err:
  145. # TODO logging
  146. return
  147. except ProtocolError as err:
  148. # TODO logging
  149. print(repr(err))
  150. return
  151. except AssertionError:
  152. # TODO logging
  153. print(repr(err))
  154. return
  155. self.transport.write(response.encode('ascii'))
  156. try:
  157. self.transport.write_eof()
  158. except NotImplementedError:
  159. pass
  160. def sighandler(loop, signame, server):
  161. print('caught {}, exiting'.format(signame))
  162. server.close()
  163. loop.stop()
  164. parser = qubes.tools.QubesArgumentParser(description='Qubes OS daemon')
  165. def main(args=None):
  166. args = parser.parse_args(args)
  167. loop = asyncio.get_event_loop()
  168. qubes.libvirtaio.LibvirtAsyncIOEventImpl(loop).register()
  169. try:
  170. os.unlink(QUBESD_SOCK)
  171. except FileNotFoundError:
  172. pass
  173. old_umask = os.umask(0o007)
  174. server = loop.run_until_complete(loop.create_unix_server(
  175. functools.partial(QubesDaemonProtocol, app=args.app), QUBESD_SOCK))
  176. os.umask(old_umask)
  177. del old_umask
  178. for signame in ('SIGINT', 'SIGTERM'):
  179. loop.add_signal_handler(getattr(signal, signame),
  180. sighandler, loop, signame, server)
  181. qubes.utils.systemd_notify()
  182. try:
  183. loop.run_forever()
  184. loop.run_until_complete(server.wait_closed())
  185. finally:
  186. loop.close()
  187. if __name__ == '__main__':
  188. main()