sd-varlink: Return empty object for missing params
Previously, sd-varlink was inconsistent about what it did with empty/missing/null parameters in a Varlink call or reply: when dispatching to a callback, it would always construct an empty object and hand over the empty object to the handler (good!). But when returning the value directly (i.e. with sd_varlink_call), it would return NULL when parameters were omitted and a JSON null when the paramerters were null. This inconsistency is a violation of UAPI.20, which states that receivers of Varlink messages MUST treat omitted/null/empty parameters on Varlink calls and replies equivalently, where senders only SHOULD prefer to omit the parameters. This commit makes sd-varlink normalize the parameters to an empty object whenever it is null or omitted, which brings sd-varlink into compliance with this rule in UAPI.20 (cherry picked from commit 34db99971e728e2c4f155c01d43d24f3fe313bef)
This commit is contained in:
committed by
Luca Boccassi
parent
adc93a4ad5
commit
ffcf226c7f
@@ -824,22 +824,30 @@ static int varlink_dispatch_disconnect(sd_varlink *v) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int varlink_sanitize_incoming_parameters(sd_json_variant **v) {
|
||||
static int varlink_normalize_incoming_parameters(sd_json_variant **framing, sd_json_variant **params) {
|
||||
int r;
|
||||
assert(v);
|
||||
|
||||
/* Convert NULL or JSON null to empty object for method handlers (backward compatibility) */
|
||||
if (!*v || sd_json_variant_is_null(*v)) {
|
||||
/* UAPI.20 specifies that receivers of Varlink messages MUST treat "parameters" identically no matter
|
||||
* if it is missing, explicitly null, or an empty object. Here, we choose to normalize "parameters"
|
||||
* to an empty object if it is missing or null, which makes sd_varlink behave predictably (i.e.
|
||||
* sd_varlink_call and similar never return write NULL to ret_parameters if they return r >= 0) */
|
||||
|
||||
assert(params);
|
||||
|
||||
if (!*params || sd_json_variant_is_null(*params)) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *empty = NULL;
|
||||
r = sd_json_variant_new_object(&empty, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return json_variant_unref_and_replace(*v, empty);
|
||||
}
|
||||
|
||||
/* Ensure we have an object */
|
||||
if (!sd_json_variant_is_object(*v))
|
||||
return -EINVAL;
|
||||
if (framing) {
|
||||
r = sd_json_variant_set_field(framing, "parameters", empty);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return json_variant_unref_and_replace(*params, empty);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -903,7 +911,7 @@ static int varlink_dispatch_reply(sd_varlink *v) {
|
||||
if (error && FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
|
||||
goto invalid;
|
||||
|
||||
r = varlink_sanitize_incoming_parameters(¶meters);
|
||||
r = varlink_normalize_incoming_parameters(&v->current, ¶meters);
|
||||
if (r < 0)
|
||||
goto invalid;
|
||||
|
||||
@@ -1269,7 +1277,7 @@ static int varlink_dispatch_method(sd_varlink *v) {
|
||||
if (!method)
|
||||
goto invalid;
|
||||
|
||||
r = varlink_sanitize_incoming_parameters(¶meters);
|
||||
r = varlink_normalize_incoming_parameters(NULL, ¶meters);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
@@ -2355,15 +2363,6 @@ _public_ int sd_varlink_collect_full(
|
||||
if (sd_json_variant_elements(collected) >= VARLINK_COLLECT_MAX)
|
||||
return varlink_log_errno(v, SYNTHETIC_ERRNO(E2BIG), "Number of reply messages grew too large (%zu) while collecting.", sd_json_variant_elements(collected));
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *empty = NULL;
|
||||
if (!p) {
|
||||
r = sd_json_variant_new_array(&empty, /* array= */ NULL, /* n= */ 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p = empty;
|
||||
}
|
||||
|
||||
r = sd_json_variant_append_array(&collected, p);
|
||||
if (r < 0)
|
||||
return varlink_log_errno(v, r, "Failed to append JSON object to array: %m");
|
||||
|
||||
@@ -380,5 +380,24 @@ echo "$result" | grep "untrusted data decompressed safely" >/dev/null
|
||||
kill "$SERVE_PID" 2>/dev/null || true
|
||||
wait "$SERVE_PID" 2>/dev/null || true
|
||||
|
||||
# Verify consistent handling of omitted/null/empty parameters
|
||||
check_output_is_empty_object() {
|
||||
REPLY="$1"
|
||||
# This is a "Varlink server" that reads the incoming call, discards it, and
|
||||
# directly sends the contents of $REPLY as the reply. This lets us exercise
|
||||
# sd-varlink's client-side parsing of the Varlink call/reply framing
|
||||
# shellcheck disable=SC2016
|
||||
SERVE_PID=$(systemd-notify --fork -- \
|
||||
systemd-socket-activate -l "$SERVE_SOCKET" --inetd -a -- \
|
||||
bash -c 'read -r -d "" _; printf "%s\0" "$0"' "$REPLY")
|
||||
varlinkctl call "unix:$SERVE_SOCKET" com.example.Test '{}' | grep "^{}$"
|
||||
varlinkctl --exec call "unix:$SERVE_SOCKET" com.example.Test '{}' -- grep "^{}$"
|
||||
kill "$SERVE_PID" 2>/dev/null || true
|
||||
wait "$SERVE_PID" 2>/dev/null || true
|
||||
}
|
||||
check_output_is_empty_object '{}'
|
||||
check_output_is_empty_object '{"parameters": null}'
|
||||
check_output_is_empty_object '{"parameters": {}}'
|
||||
|
||||
rm -f "$SERVE_SOCKET"
|
||||
rm -rf "$(dirname "$SERVE_SOCKET")" "$SERVE_TMPDIR"
|
||||
|
||||
Reference in New Issue
Block a user