Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Be stricter about JSON that is accepted by Synapse #8106

Merged
merged 5 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/8106.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a long-standing bug where invalid JSON would be accepted by Synapse.
6 changes: 3 additions & 3 deletions synapse/api/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
from http import HTTPStatus
from typing import Dict, List, Optional, Union

from canonicaljson import json

from twisted.web import http

from synapse.util import json_decoder

if typing.TYPE_CHECKING:
from synapse.types import JsonDict

Expand Down Expand Up @@ -593,7 +593,7 @@ def to_synapse_error(self):
# try to parse the body as json, to get better errcode/msg, but
# default to M_UNKNOWN with the HTTP status as the error text
try:
j = json.loads(self.response.decode("utf-8"))
j = json_decoder.decode(self.response.decode("utf-8"))
except ValueError:
j = {}

Expand Down
5 changes: 2 additions & 3 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
Union,
)

from canonicaljson import json
from prometheus_client import Counter, Histogram

from twisted.internet import defer
Expand Down Expand Up @@ -63,7 +62,7 @@
ReplicationGetQueryRestServlet,
)
from synapse.types import JsonDict, get_domain_from_id
from synapse.util import glob_to_regex, unwrapFirstError
from synapse.util import glob_to_regex, json_decoder, unwrapFirstError
from synapse.util.async_helpers import Linearizer, concurrently_execute
from synapse.util.caches.response_cache import ResponseCache

Expand Down Expand Up @@ -551,7 +550,7 @@ async def on_claim_client_keys(
for device_id, keys in device_keys.items():
for key_id, json_str in keys.items():
json_result.setdefault(user_id, {})[device_id] = {
key_id: json.loads(json_str)
key_id: json_decoder.decode(json_str)
}

logger.info(
Expand Down
5 changes: 2 additions & 3 deletions synapse/federation/sender/transaction_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
import logging
from typing import TYPE_CHECKING, List, Tuple

from canonicaljson import json

from synapse.api.errors import HttpResponseException
from synapse.events import EventBase
from synapse.federation.persistence import TransactionActions
Expand All @@ -28,6 +26,7 @@
tags,
whitelisted_homeserver,
)
from synapse.util import json_decoder
from synapse.util.metrics import measure_func

if TYPE_CHECKING:
Expand Down Expand Up @@ -71,7 +70,7 @@ async def send_new_transaction(
for edu in pending_edus:
context = edu.get_context()
if context:
span_contexts.append(extract_text_map(json.loads(context)))
span_contexts.append(extract_text_map(json_decoder.decode(context)))
if keep_destination:
edu.strip_context()

Expand Down
8 changes: 4 additions & 4 deletions synapse/handlers/e2e_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from typing import Dict, List, Optional, Tuple

import attr
from canonicaljson import encode_canonical_json, json
from canonicaljson import encode_canonical_json
from signedjson.key import VerifyKey, decode_verify_key_bytes
from signedjson.sign import SignatureVerifyException, verify_signed_json
from unpaddedbase64 import decode_base64
Expand All @@ -35,7 +35,7 @@
get_domain_from_id,
get_verify_key_from_cross_signing_key,
)
from synapse.util import unwrapFirstError
from synapse.util import json_decoder, unwrapFirstError
from synapse.util.async_helpers import Linearizer
from synapse.util.caches.expiringcache import ExpiringCache
from synapse.util.retryutils import NotRetryingDestination
Expand Down Expand Up @@ -404,7 +404,7 @@ async def claim_one_time_keys(self, query, timeout):
for device_id, keys in device_keys.items():
for key_id, json_bytes in keys.items():
json_result.setdefault(user_id, {})[device_id] = {
key_id: json.loads(json_bytes)
key_id: json_decoder.decode(json_bytes)
}

@trace
Expand Down Expand Up @@ -1186,7 +1186,7 @@ def _exception_to_failure(e):


def _one_time_keys_match(old_key_json, new_key):
old_key = json.loads(old_key_json)
old_key = json_decoder.decode(old_key_json)

# if either is a string rather than an object, they must match exactly
if not isinstance(old_key, dict) or not isinstance(new_key, dict):
Expand Down
5 changes: 2 additions & 3 deletions synapse/handlers/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import urllib.parse
from typing import Awaitable, Callable, Dict, List, Optional, Tuple

from canonicaljson import json

from twisted.internet.error import TimeoutError

from synapse.api.errors import (
Expand All @@ -34,6 +32,7 @@
from synapse.config.emailconfig import ThreepidBehaviour
from synapse.http.client import SimpleHttpClient
from synapse.types import JsonDict, Requester
from synapse.util import json_decoder
from synapse.util.hash import sha256_and_url_safe_base64
from synapse.util.stringutils import assert_valid_client_secret, random_string

Expand Down Expand Up @@ -177,7 +176,7 @@ async def bind_threepid(
except TimeoutError:
raise SynapseError(500, "Timed out contacting identity server")
except CodeMessageException as e:
data = json.loads(e.msg) # XXX WAT?
data = json_decoder.decode(e.msg) # XXX WAT?
return data

logger.info("Got 404 when POSTing JSON %s, falling back to v1 URL", bind_url)
Expand Down
5 changes: 3 additions & 2 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple

from canonicaljson import encode_canonical_json, json
from canonicaljson import encode_canonical_json

from twisted.internet.interfaces import IDelayedCall

Expand Down Expand Up @@ -55,6 +55,7 @@
UserID,
create_requester,
)
from synapse.util import json_decoder
from synapse.util.async_helpers import Linearizer
from synapse.util.frozenutils import frozendict_json_encoder
from synapse.util.metrics import measure_func
Expand Down Expand Up @@ -859,7 +860,7 @@ async def handle_new_client_event(
# Ensure that we can round trip before trying to persist in db
try:
dump = frozendict_json_encoder.encode(event.content)
json.loads(dump)
json_decoder.decode(dump)
except Exception:
logger.exception("Failed to encode content: %r", event.content)
raise
Expand Down
6 changes: 3 additions & 3 deletions synapse/handlers/oidc_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import logging
from typing import TYPE_CHECKING, Dict, Generic, List, Optional, Tuple, TypeVar
from urllib.parse import urlencode
Expand All @@ -39,6 +38,7 @@
from synapse.http.site import SynapseRequest
from synapse.logging.context import make_deferred_yieldable
from synapse.types import UserID, map_username_to_mxid_localpart
from synapse.util import json_decoder

if TYPE_CHECKING:
from synapse.server import HomeServer
Expand Down Expand Up @@ -367,7 +367,7 @@ async def _exchange_code(self, code: str) -> Token:
# and check for an error field. If not, we respond with a generic
# error message.
try:
resp = json.loads(resp_body.decode("utf-8"))
resp = json_decoder.decode(resp_body.decode("utf-8"))
error = resp["error"]
description = resp.get("error_description", error)
except (ValueError, KeyError):
Expand All @@ -384,7 +384,7 @@ async def _exchange_code(self, code: str) -> Token:

# Since it is a not a 5xx code, body should be a valid JSON. It will
# raise if not.
resp = json.loads(resp_body.decode("utf-8"))
resp = json_decoder.decode(resp_body.decode("utf-8"))

if "error" in resp:
error = resp["error"]
Expand Down
5 changes: 2 additions & 3 deletions synapse/handlers/ui_auth/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
import logging
from typing import Any

from canonicaljson import json

from twisted.web.client import PartialDownloadError

from synapse.api.constants import LoginType
from synapse.api.errors import Codes, LoginError, SynapseError
from synapse.config.emailconfig import ThreepidBehaviour
from synapse.util import json_decoder

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -117,7 +116,7 @@ async def check_auth(self, authdict, clientip):
except PartialDownloadError as pde:
# Twisted is silly
data = pde.response
resp_body = json.loads(data.decode("utf-8"))
resp_body = json_decoder.decode(data.decode("utf-8"))

if "success" in resp_body:
# Note that we do NOT check the hostname here: we explicitly
Expand Down
11 changes: 6 additions & 5 deletions synapse/http/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from io import BytesIO

import treq
from canonicaljson import encode_canonical_json, json
from canonicaljson import encode_canonical_json
from netaddr import IPAddress
from prometheus_client import Counter
from zope.interface import implementer, provider
Expand Down Expand Up @@ -47,6 +47,7 @@
from synapse.http.proxyagent import ProxyAgent
from synapse.logging.context import make_deferred_yieldable
from synapse.logging.opentracing import set_tag, start_active_span, tags
from synapse.util import json_decoder
from synapse.util.async_helpers import timeout_deferred

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -391,7 +392,7 @@ async def post_urlencoded_get_json(self, uri, args={}, headers=None):
body = await make_deferred_yieldable(readBody(response))

if 200 <= response.code < 300:
return json.loads(body.decode("utf-8"))
return json_decoder.decode(body.decode("utf-8"))
else:
raise HttpResponseException(
response.code, response.phrase.decode("ascii", errors="replace"), body
Expand Down Expand Up @@ -433,7 +434,7 @@ async def post_json_get_json(self, uri, post_json, headers=None):
body = await make_deferred_yieldable(readBody(response))

if 200 <= response.code < 300:
return json.loads(body.decode("utf-8"))
return json_decoder.decode(body.decode("utf-8"))
else:
raise HttpResponseException(
response.code, response.phrase.decode("ascii", errors="replace"), body
Expand Down Expand Up @@ -463,7 +464,7 @@ async def get_json(self, uri, args={}, headers=None):
actual_headers.update(headers)

body = await self.get_raw(uri, args, headers=headers)
return json.loads(body.decode("utf-8"))
return json_decoder.decode(body.decode("utf-8"))

async def put_json(self, uri, json_body, args={}, headers=None):
""" Puts some json to the given URI.
Expand Down Expand Up @@ -506,7 +507,7 @@ async def put_json(self, uri, json_body, args={}, headers=None):
body = await make_deferred_yieldable(readBody(response))

if 200 <= response.code < 300:
return json.loads(body.decode("utf-8"))
return json_decoder.decode(body.decode("utf-8"))
else:
raise HttpResponseException(
response.code, response.phrase.decode("ascii", errors="replace"), body
Expand Down
5 changes: 2 additions & 3 deletions synapse/http/federation/well_known_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import random
import time
Expand All @@ -26,7 +25,7 @@
from twisted.web.http_headers import Headers

from synapse.logging.context import make_deferred_yieldable
from synapse.util import Clock
from synapse.util import Clock, json_decoder
from synapse.util.caches.ttlcache import TTLCache
from synapse.util.metrics import Measure

Expand Down Expand Up @@ -181,7 +180,7 @@ def _fetch_well_known(self, server_name):
if response.code != 200:
raise Exception("Non-200 response %s" % (response.code,))

parsed_body = json.loads(body.decode("utf-8"))
parsed_body = json_decoder.decode(body.decode("utf-8"))
logger.info("Response from .well-known: %s", parsed_body)

result = parsed_body["m.server"].encode("ascii")
Expand Down
5 changes: 2 additions & 3 deletions synapse/http/servlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@

import logging

from canonicaljson import json

from synapse.api.errors import Codes, SynapseError
from synapse.util import json_decoder

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -215,7 +214,7 @@ def parse_json_value_from_request(request, allow_empty_body=False):
return None

try:
content = json.loads(content_bytes.decode("utf-8"))
content = json_decoder.decode(content_bytes.decode("utf-8"))
except Exception as e:
logger.warning("Unable to parse JSON: %s", e)
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
Expand Down
7 changes: 5 additions & 2 deletions synapse/logging/opentracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def set_fates(clotho, lachesis, atropos, father="Zues", mother="Themis"):
from twisted.internet import defer

from synapse.config import ConfigError
from synapse.util import json_decoder

if TYPE_CHECKING:
from synapse.http.site import SynapseRequest
Expand Down Expand Up @@ -499,7 +500,9 @@ def start_active_span_from_edu(
if opentracing is None:
return _noop_context_manager()

carrier = json.loads(edu_content.get("context", "{}")).get("opentracing", {})
carrier = json_decoder.decode(edu_content.get("context", "{}")).get(
"opentracing", {}
)
context = opentracing.tracer.extract(opentracing.Format.TEXT_MAP, carrier)
_references = [
opentracing.child_of(span_context_from_string(x))
Expand Down Expand Up @@ -699,7 +702,7 @@ def span_context_from_string(carrier):
Returns:
The active span context decoded from a string.
"""
carrier = json.loads(carrier)
carrier = json_decoder.decode(carrier)
return opentracing.tracer.extract(opentracing.Format.TEXT_MAP, carrier)


Expand Down
12 changes: 5 additions & 7 deletions synapse/replication/tcp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
import logging
from typing import Tuple, Type

from canonicaljson import json

from synapse.util import json_encoder as _json_encoder
from synapse.util import json_decoder, json_encoder

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -125,7 +123,7 @@ def from_line(cls, line):
stream_name,
instance_name,
None if token == "batch" else int(token),
json.loads(row_json),
json_decoder.decode(row_json),
)

def to_line(self):
Expand All @@ -134,7 +132,7 @@ def to_line(self):
self.stream_name,
self.instance_name,
str(self.token) if self.token is not None else "batch",
_json_encoder.encode(self.row),
json_encoder.encode(self.row),
)
)

Expand Down Expand Up @@ -359,15 +357,15 @@ def __init__(self, user_id, access_token, ip, user_agent, device_id, last_seen):
def from_line(cls, line):
user_id, jsn = line.split(" ", 1)

access_token, ip, user_agent, device_id, last_seen = json.loads(jsn)
access_token, ip, user_agent, device_id, last_seen = json_decoder.decode(jsn)

return cls(user_id, access_token, ip, user_agent, device_id, last_seen)

def to_line(self):
return (
self.user_id
+ " "
+ _json_encoder.encode(
+ json_encoder.encode(
(
self.access_token,
self.ip,
Expand Down
Loading