*/
#define MAX_OAUTH_RESPONSE_SIZE (256 * 1024)
+/*
+ * Similarly, a limit on the maximum JSON nesting level keeps a server from
+ * running us out of stack space. A common nesting level in practice is 2 (for a
+ * top-level object containing arrays of strings). As of May 2025, the maximum
+ * depth for standard server metadata appears to be 6, if the document contains
+ * a full JSON Web Key Set in its "jwks" parameter.
+ *
+ * Since it's easy to nest JSON, and the number of parameters and key types
+ * keeps growing, take a healthy buffer of 16. (If this ever proves to be a
+ * problem in practice, we may want to switch over to the incremental JSON
+ * parser instead of playing with this parameter.)
+ */
+#define MAX_OAUTH_NESTING_LEVEL 16
+
/*
* Parsed JSON Representations
*
}
++ctx->nested;
+ if (ctx->nested > MAX_OAUTH_NESTING_LEVEL)
+ {
+ oauth_parse_set_error(ctx, "JSON is too deeply nested");
+ return JSON_SEM_ACTION_FAILED;
+ }
+
return JSON_SUCCESS;
}
}
++ctx->nested;
+ if (ctx->nested > MAX_OAUTH_NESTING_LEVEL)
+ {
+ oauth_parse_set_error(ctx, "JSON is too deeply nested");
+ return JSON_SEM_ACTION_FAILED;
+ }
+
return JSON_SUCCESS;
}
#define ERROR_SCOPE_FIELD "scope"
#define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
+/*
+ * Limit the maximum number of nested objects/arrays. Because OAUTHBEARER
+ * doesn't have any defined extensions for its JSON yet, we can be much more
+ * conservative here than with libpq-oauth's MAX_OAUTH_NESTING_LEVEL; we expect
+ * a nesting level of 1 in practice.
+ */
+#define MAX_SASL_NESTING_LEVEL 8
+
struct json_ctx
{
char *errmsg; /* any non-NULL value stops all processing */
}
++ctx->nested;
+ if (ctx->nested > MAX_SASL_NESTING_LEVEL)
+ oauth_json_set_error(ctx, libpq_gettext("JSON is too deeply nested"));
+
return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
}
ctx->target_field_name);
}
+ ++ctx->nested;
+ if (ctx->nested > MAX_SASL_NESTING_LEVEL)
+ oauth_json_set_error(ctx, libpq_gettext("JSON is too deeply nested"));
+
return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
}
+static JsonParseErrorType
+oauth_json_array_end(void *state)
+{
+ struct json_ctx *ctx = state;
+
+ --ctx->nested;
+ return JSON_SUCCESS;
+}
+
static JsonParseErrorType
oauth_json_scalar(void *state, char *token, JsonTokenType type)
{
sem.object_end = oauth_json_object_end;
sem.object_field_start = oauth_json_object_field_start;
sem.array_start = oauth_json_array_start;
+ sem.array_end = oauth_json_array_end;
sem.scalar = oauth_json_scalar;
err = pg_parse_json(lex, &sem);
expected_stderr =>
qr/failed to obtain access token: response is too large/);
+my $nesting_limit = 16;
+$node->connect_ok(
+ connstr(
+ stage => 'device',
+ nested_array => $nesting_limit,
+ nested_object => $nesting_limit),
+ "nested arrays and objects, up to parse limit",
+ expected_stderr =>
+ qr@Visit https://example\.com/ and enter the code: postgresuser@);
+$node->connect_fails(
+ connstr(stage => 'device', nested_array => $nesting_limit + 1),
+ "bad discovery response: overly nested JSON array",
+ expected_stderr =>
+ qr/failed to parse device authorization: JSON is too deeply nested/);
+$node->connect_fails(
+ connstr(stage => 'device', nested_object => $nesting_limit + 1),
+ "bad discovery response: overly nested JSON object",
+ expected_stderr =>
+ qr/failed to parse device authorization: JSON is too deeply nested/);
+
$node->connect_fails(
connstr(stage => 'device', content_type => 'text/plain'),
"bad device authz response: wrong content type",
#
import base64
+import functools
import http.server
import json
import os
@property
def _response_padding(self):
"""
- If the huge_response test parameter is set to True, returns a dict
- containing a gigantic string value, which can then be folded into a JSON
- response.
+ Returns a dict with any additional entries that should be folded into a
+ JSON response, as determined by test parameters provided by the client:
+
+ - huge_response: if set to True, the dict will contain a gigantic string
+ value
+
+ - nested_array: if set to nonzero, the dict will contain a deeply nested
+ array so that the top-level object has the given depth
+
+ - nested_object: if set to nonzero, the dict will contain a deeply
+ nested JSON object so that the top-level object has the given depth
"""
- if not self._get_param("huge_response", False):
- return dict()
+ ret = dict()
+
+ if self._get_param("huge_response", False):
+ ret["_pad_"] = "x" * 1024 * 1024
+
+ depth = self._get_param("nested_array", 0)
+ if depth:
+ ret["_arr_"] = functools.reduce(lambda x, _: [x], range(depth))
+
+ depth = self._get_param("nested_object", 0)
+ if depth:
+ ret["_obj_"] = functools.reduce(lambda x, _: {"": x}, range(depth))
- return {"_pad_": "x" * 1024 * 1024}
+ return ret
@property
def _access_token(self):