Implement cancellation support/protection for module callbacks (#12568)

There's no guarantee that module callbacks will handle cancellation
appropriately. Protect module callbacks with read semantics from
cancellation and avoid swallowing `CancelledError`s that arise.

Other module callbacks, such as the `on_*` callbacks, are presumed to
live on code paths that involve writes and aren't cancellation-friendly.
These module callbacks have been left alone.

Signed-off-by: Sean Quah <seanq@element.io>
This commit is contained in:
Sean Quah 2022-05-09 12:31:14 +01:00 committed by GitHub
parent 8de0facaae
commit a00462dd99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 27 deletions

View file

@ -41,6 +41,7 @@ import pymacaroons
import unpaddedbase64
from pymacaroons.exceptions import MacaroonVerificationFailedException
from twisted.internet.defer import CancelledError
from twisted.web.server import Request
from synapse.api.constants import LoginType
@ -67,7 +68,7 @@ from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.storage.roommember import ProfileInfo
from synapse.types import JsonDict, Requester, UserID
from synapse.util import stringutils as stringutils
from synapse.util.async_helpers import maybe_awaitable
from synapse.util.async_helpers import delay_cancellation, maybe_awaitable
from synapse.util.macaroons import get_value_from_macaroon, satisfy_expiry
from synapse.util.msisdn import phone_number_to_msisdn
from synapse.util.stringutils import base62_encode
@ -2202,7 +2203,11 @@ class PasswordAuthProvider:
# other than None (i.e. until a callback returns a success)
for callback in self.auth_checker_callbacks[login_type]:
try:
result = await callback(username, login_type, login_dict)
result = await delay_cancellation(
callback(username, login_type, login_dict)
)
except CancelledError:
raise
except Exception as e:
logger.warning("Failed to run module API callback %s: %s", callback, e)
continue
@ -2263,7 +2268,9 @@ class PasswordAuthProvider:
for callback in self.check_3pid_auth_callbacks:
try:
result = await callback(medium, address, password)
result = await delay_cancellation(callback(medium, address, password))
except CancelledError:
raise
except Exception as e:
logger.warning("Failed to run module API callback %s: %s", callback, e)
continue
@ -2345,7 +2352,7 @@ class PasswordAuthProvider:
"""
for callback in self.get_username_for_registration_callbacks:
try:
res = await callback(uia_results, params)
res = await delay_cancellation(callback(uia_results, params))
if isinstance(res, str):
return res
@ -2359,6 +2366,8 @@ class PasswordAuthProvider:
callback,
res,
)
except CancelledError:
raise
except Exception as e:
logger.error(
"Module raised an exception in get_username_for_registration: %s",
@ -2388,7 +2397,7 @@ class PasswordAuthProvider:
"""
for callback in self.get_displayname_for_registration_callbacks:
try:
res = await callback(uia_results, params)
res = await delay_cancellation(callback(uia_results, params))
if isinstance(res, str):
return res
@ -2402,6 +2411,8 @@ class PasswordAuthProvider:
callback,
res,
)
except CancelledError:
raise
except Exception as e:
logger.error(
"Module raised an exception in get_displayname_for_registration: %s",
@ -2429,7 +2440,7 @@ class PasswordAuthProvider:
"""
for callback in self.is_3pid_allowed_callbacks:
try:
res = await callback(medium, address, registration)
res = await delay_cancellation(callback(medium, address, registration))
if res is False:
return res
@ -2443,6 +2454,8 @@ class PasswordAuthProvider:
callback,
res,
)
except CancelledError:
raise
except Exception as e:
logger.error("Module raised an exception in is_3pid_allowed: %s", e)
raise SynapseError(code=500, msg="Internal Server Error")