sd-json: Fix validation of optional fields within a mandatory struct

If a varlink method takes a struct/object as a parameter, and it’s
marked as `SD_JSON_MANDATORY`, and it has an optional field inside it
which is *not* marked as `SD_JSON_MANDATORY`, we want to not require
that field to be set.

Previously, due to using the `merged_flags` from both the mandatory
struct and the optional field, `SD_JSON_MANDATORY` was effectively
always set on the optional field even if we didn’t want it. This
resulted in an error being emitted if the mandatory struct was provided
in a varlink call, but without the optional field.

Fix that by validating the field’s presence only against its own flags
and not also the flags of its parent.

Adds a unit test to prevent regressions.
This commit is contained in:
Philip Withnall
2026-06-26 13:01:22 +01:00
parent 28b9b5712a
commit 0b3eeb1547
2 changed files with 73 additions and 1 deletions
+1 -1
View File
@@ -5359,7 +5359,7 @@ _public_ int sd_json_dispatch_full(
for (const sd_json_dispatch_field *p = table; p && p->name; p++) {
sd_json_dispatch_flags_t merged_flags = p->flags | flags;
if ((merged_flags & SD_JSON_MANDATORY) && !found[p-table]) {
if ((p->flags & SD_JSON_MANDATORY) && !found[p-table]) {
json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name);
if ((merged_flags & SD_JSON_PERMISSIVE))
+72
View File
@@ -452,6 +452,78 @@ TEST(invalid_parameter) {
ASSERT_OK(sd_event_loop(e));
}
typedef struct MandatoryTypeWithOptionalField {
const char *mandatory;
const char *optional;
} MandatoryTypeWithOptionalField;
static int dispatch_mandatory_type_with_optional_field(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
MandatoryTypeWithOptionalField *m = ASSERT_PTR(userdata);
static const sd_json_dispatch_field dispatch[] = {
{ "mandatory", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(*m, mandatory), SD_JSON_MANDATORY },
{ "optional", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(*m, optional), 0 },
{}
};
return sd_json_dispatch(variant, dispatch, flags, m);
}
static int method_mandatory_type_with_optional_field(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
MandatoryTypeWithOptionalField m = { NULL, NULL };
int r;
sd_json_dispatch_field table[] = {
{ "mandatoryField", SD_JSON_VARIANT_OBJECT, dispatch_mandatory_type_with_optional_field, 0, SD_JSON_MANDATORY },
{}
};
r = sd_varlink_dispatch(link, parameters, table, &m);
if (r != 0)
return r;
_cleanup_free_ char *sum = strjoin(m.mandatory, "+", m.optional);
if (!sum)
return log_oom();
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("result", sum));
}
static int reply_valid_hi(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) {
ASSERT_NULL(error_id);
ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(parameters, "result")), "hi+");
ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS));
return 0;
}
TEST(mandatory_type_with_optional_field) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
ASSERT_OK(sd_event_default(&e));
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
ASSERT_OK(sd_varlink_server_new(&s, 0));
ASSERT_OK(sd_varlink_server_attach_event(s, e, 0));
ASSERT_OK(sd_varlink_server_bind_method(s, "foo.mytest.MandatoryTypeWithOptionalField", method_mandatory_type_with_optional_field));
int connfd[2];
ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd));
ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL));
_cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL;
ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1]));
ASSERT_OK(sd_varlink_attach_event(c, e, 0));
ASSERT_OK(sd_varlink_bind_reply(c, reply_valid_hi));
ASSERT_OK(sd_varlink_invokebo(c, "foo.mytest.MandatoryTypeWithOptionalField",
SD_JSON_BUILD_PAIR_OBJECT("mandatoryField",
SD_JSON_BUILD_PAIR_STRING("mandatory", "hi"))));
ASSERT_OK(sd_event_loop(e));
}
static int method_with_error_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
/* Set an error sentinel and return without sending a reply. The sentinel error should be sent automatically. */
ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError"));