qubesd.py 6.8 KB

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