|
27 | 27 | import re |
28 | 28 | import shutil |
29 | 29 | import socket |
| 30 | +import string |
30 | 31 | import struct |
31 | 32 | import traceback |
32 | 33 | from typing import Union, Any |
@@ -189,6 +190,11 @@ def __init__( |
189 | 190 | self._handler = candidates[0] |
190 | 191 | self._running_handler = None |
191 | 192 |
|
| 193 | + # pylint: disable=invalid-name |
| 194 | + self.EXC_ARG_NOT_IN_DEST_VOLUMES = "{} volumes".format(self.dest.name) |
| 195 | + # pylint: disable=invalid-name |
| 196 | + self.EXC_ARG_NOT_IN_POOLS = "pools" |
| 197 | + |
192 | 198 | @classmethod |
193 | 199 | def list_methods(cls, select_method=None): |
194 | 200 | for attr in dir(cls): |
@@ -239,26 +245,76 @@ def fire_event_for_filter(self, iterable, **kwargs): |
239 | 245 | """Fire an event on the source qube to filter for permission""" |
240 | 246 | return apply_filters(iterable, self.fire_event_for_permission(**kwargs)) |
241 | 247 |
|
| 248 | + def enforce_arg( |
| 249 | + self, |
| 250 | + wants: Union[None, bool, str, list, tuple] = None, |
| 251 | + short_reason: str = "", |
| 252 | + ) -> None: |
| 253 | + """Enforce argument to be absent or be in iterable.""" |
| 254 | + if wants is None: |
| 255 | + self.enforce(not self.arg, reason="Argument is present") |
| 256 | + elif wants is True: |
| 257 | + self.enforce(self.arg, reason="Argument is empty") |
| 258 | + else: |
| 259 | + assert isinstance(self.arg, str) |
| 260 | + assert short_reason |
| 261 | + if wants is None or isinstance(wants, bool): |
| 262 | + assertion = self.arg is wants |
| 263 | + elif isinstance(wants, str): |
| 264 | + assertion = self.arg == wants |
| 265 | + else: |
| 266 | + assertion = self.arg in wants |
| 267 | + self.enforce( |
| 268 | + assertion, |
| 269 | + reason="Argument not in {!r}".format(short_reason), |
| 270 | + ) |
| 271 | + |
| 272 | + def enforce_dest_dom0(self, wants: bool = True) -> None: |
| 273 | + """Enforce destination to be or not to be dom0.""" |
| 274 | + if wants: |
| 275 | + self.enforce( |
| 276 | + self.dest.name == "dom0", reason="Destination is not dom0" |
| 277 | + ) |
| 278 | + else: |
| 279 | + self.enforce(self.dest.name != "dom0", reason="Destination is dom0") |
| 280 | + |
242 | 281 | @staticmethod |
243 | | - def enforce(predicate): |
244 | | - """An assert replacement, but works even with optimisations.""" |
| 282 | + def enforce(predicate, reason: str = "") -> None: |
| 283 | + """If predicate is false, raise an exception to terminate handling |
| 284 | + the request. |
| 285 | +
|
| 286 | + This will raise :py:class:`ProtocolError` if the predicate is false. |
| 287 | + See the documentation of that class for details. |
| 288 | +
|
| 289 | + :param str reason: Exception motive. |
| 290 | + """ |
245 | 291 | if not predicate: |
246 | | - raise PermissionDenied() |
| 292 | + raise ProtocolError(reason) |
247 | 293 |
|
248 | 294 | def validate_size( |
249 | | - self, untrusted_size: bytes, allow_negative: bool = False |
| 295 | + self, untrusted_size: bytes, name: str, allow_negative: bool = False |
250 | 296 | ) -> int: |
251 | | - self.enforce(isinstance(untrusted_size, bytes)) |
| 297 | + allowed_chars = string.ascii_letters + string.digits + "-_. " |
| 298 | + assert all(c in allowed_chars for c in name) |
| 299 | + if not isinstance(untrusted_size, bytes): |
| 300 | + raise ProtocolError( |
| 301 | + "Expected {!r} to be of type bytes, got {!r}".format( |
| 302 | + name, type(untrusted_size).__name__ |
| 303 | + ) |
| 304 | + ) |
252 | 305 | coefficient = 1 |
253 | 306 | if allow_negative and untrusted_size.startswith(b"-"): |
254 | 307 | coefficient = -1 |
255 | 308 | untrusted_size = untrusted_size[1:] |
| 309 | + name_cap = name.capitalize() |
256 | 310 | if not untrusted_size.isdigit(): |
257 | | - raise qubes.exc.ProtocolError("Size must be ASCII digits (only)") |
| 311 | + raise ProtocolError(name_cap + " size contains non ASCII digits") |
258 | 312 | if len(untrusted_size) >= 20: |
259 | | - raise qubes.exc.ProtocolError("Sizes limited to 19 decimal digits") |
| 313 | + raise ProtocolError( |
| 314 | + name_cap + " size is bigger than 19 decimal digits" |
| 315 | + ) |
260 | 316 | if untrusted_size[0] == 48 and untrusted_size != b"0": |
261 | | - raise qubes.exc.ProtocolError("Spurious leading zeros not allowed") |
| 317 | + raise ProtocolError(name_cap + " contains spurious leading zeros") |
262 | 318 | return int(untrusted_size) * coefficient |
263 | 319 |
|
264 | 320 |
|
@@ -345,21 +401,15 @@ async def respond(self, src, meth, dest, arg, *, untrusted_payload): |
345 | 401 |
|
346 | 402 | # except clauses will fall through to transport.abort() below |
347 | 403 |
|
348 | | - except PermissionDenied: |
349 | | - self.app.log.warning( |
350 | | - "permission denied for call %s+%s (%s → %s) " |
351 | | - "with payload of %d bytes", |
352 | | - meth, |
353 | | - arg, |
354 | | - src, |
355 | | - dest, |
356 | | - len(untrusted_payload), |
357 | | - ) |
358 | | - |
359 | | - except ProtocolError: |
| 404 | + except (PermissionDenied, ProtocolError) as exc: |
| 405 | + log_prefix = None |
| 406 | + if isinstance(exc, PermissionDenied): |
| 407 | + log_prefix = "permission denied" |
| 408 | + elif isinstance(exc, ProtocolError): |
| 409 | + log_prefix = "protocol error" |
360 | 410 | self.app.log.warning( |
361 | | - "protocol error for call %s+%s (%s → %s) " |
362 | | - "with payload of %d bytes", |
| 411 | + "%s for call %s+%s (%s → %s) with payload of %d bytes", |
| 412 | + log_prefix, |
363 | 413 | meth, |
364 | 414 | arg, |
365 | 415 | src, |
|
0 commit comments