From: Lennart Poettering Date: Thu, 19 Mar 2026 10:23:45 +0000 (+0100) Subject: sd-json: when parsing optionally insist top-level variant is object or array X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2c74a91cb87a8f14975c899d52d32309974e846b;p=thirdparty%2Fsystemd.git sd-json: when parsing optionally insist top-level variant is object or array Typically, the top-level JSON object has to be an object, in any json document we parse, hence let's add a simple way to enforce that. Make use of this in various places. (Note, various other JSON parsers insist on this logic right from the beginning, but I actually thinking making this insisting optional like this patch does it is the cleaner approach) --- diff --git a/man/rules/meson.build b/man/rules/meson.build index d7cbd5b6520..d2d26abe5da 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -861,6 +861,17 @@ manpages = [ 'sd_json_dispatch_variant', 'sd_json_dispatch_variant_noref'], ''], + ['sd_json_parse', + '3', + ['SD_JSON_PARSE_MUST_BE_ARRAY', + 'SD_JSON_PARSE_MUST_BE_OBJECT', + 'SD_JSON_PARSE_SENSITIVE', + 'sd_json_parse_continue', + 'sd_json_parse_file', + 'sd_json_parse_file_at', + 'sd_json_parse_with_source', + 'sd_json_parse_with_source_continue'], + ''], ['sd_listen_fds', '3', ['SD_LISTEN_FDS_START', 'sd_listen_fds_with_names'], diff --git a/man/sd_json_parse.xml b/man/sd_json_parse.xml new file mode 100644 index 00000000000..c5234734dab --- /dev/null +++ b/man/sd_json_parse.xml @@ -0,0 +1,248 @@ + + + + + + + + sd_json_parse + systemd + + + + sd_json_parse + 3 + + + + sd_json_parse + sd_json_parse_continue + sd_json_parse_with_source + sd_json_parse_with_source_continue + sd_json_parse_file + sd_json_parse_file_at + SD_JSON_PARSE_SENSITIVE + SD_JSON_PARSE_MUST_BE_OBJECT + SD_JSON_PARSE_MUST_BE_ARRAY + + Parse JSON strings and files into JSON variant objects + + + + + #include <systemd/sd-json.h> + + + int sd_json_parse + const char *string + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_continue + const char **p + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_with_source + const char *string + const char *source + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_with_source_continue + const char **p + const char *source + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_file + FILE *f + const char *path + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + int sd_json_parse_file_at + FILE *f + int dir_fd + const char *path + sd_json_parse_flags_t flags + sd_json_variant **ret + unsigned *reterr_line + unsigned *reterr_column + + + + + + Description + + sd_json_parse() parses the JSON string in string and + returns the resulting JSON variant object in ret. The input must contain exactly + one JSON value (object, array, string, number, boolean, or null); any trailing non-whitespace content + after the first parsed value is considered an error. + + If parsing fails, the reterr_line and reterr_column + arguments are set to the line and column (both one-based) where the parse error occurred. One or both + may be passed as NULL if the caller is not interested in error location + information. On success, the return value is non-negative and ret is set to a + newly allocated JSON variant object (which must be freed with + sd_json_variant_unref3 + when no longer needed). ret may be passed as NULL, in which + case the input is validated but no object is returned. + + sd_json_parse_continue() is similar, but is intended for parsing a sequence of + concatenated JSON values from a single input string. Instead of taking a const char * string + directly, it takes a pointer to a const char * pointer. After each successful parse, the + pointer is advanced past the consumed input, so that subsequent calls will parse the next JSON + value. This is useful for parsing newline-delimited JSON (NDJSON) streams or similar concatenated JSON + formats. Unlike sd_json_parse(), trailing content after the first JSON value is not + considered an error — it is expected to be the beginning of the next value. + + sd_json_parse_with_source() and + sd_json_parse_with_source_continue() are similar to + sd_json_parse() and sd_json_parse_continue(), respectively, but + take an additional source argument. This is a human-readable string (typically a + file name or other origin identifier) that is attached to the parsed JSON variant object and can later be + retrieved via + sd_json_variant_get_source3. If + source is NULL, no source information is + attached. sd_json_parse() and sd_json_parse_continue() are + equivalent to calling their _with_source counterparts with + source set to NULL. + + sd_json_parse_file() reads and parses a JSON value from a file. If the + f argument is non-NULL, the JSON text is read from the + specified FILE stream. If f is NULL, the file + indicated by path is opened and read instead. The path + argument serves a dual purpose: it is both used for opening the file (if f is + NULL) and recorded as source information in the resulting JSON variant (see + above). + + sd_json_parse_file_at() is similar to + sd_json_parse_file(), but takes an additional dir_fd argument + which specifies a file descriptor referring to the directory to resolve relative paths specified in + path against. If set to AT_FDCWD, relative paths are resolved + against the current working directory, which is the default behaviour of + sd_json_parse_file(). + + The flags argument is a bitmask of zero or more of the following + flags: + + + + SD_JSON_PARSE_SENSITIVE + + Marks the resulting JSON variant as "sensitive", indicating that it contains secret + key material or similar confidential data. Sensitive variants are erased from memory when freed and + are excluded from certain debug logging and introspection operations. See + sd_json_variant_sensitive3 + for details. + + + + SD_JSON_PARSE_MUST_BE_OBJECT + + Requires that the top-level JSON value be a JSON object (i.e. {…}). + If the top-level value is an array, string, number, boolean, or null, parsing fails with + -EINVAL. + + + + + + SD_JSON_PARSE_MUST_BE_ARRAY + + Requires that the top-level JSON value be a JSON array (i.e. […]). + If the top-level value is an object, string, number, boolean, or null, parsing fails with + -EINVAL. + + + + + + If both SD_JSON_PARSE_MUST_BE_OBJECT and + SD_JSON_PARSE_MUST_BE_ARRAY are set, both objects and arrays are accepted, but + non-container values (strings, numbers, booleans, null) are still refused. + + + + Return Value + + On success, these functions return 0. On failure, they return a negative errno-style error + code. + + + Errors + + Returned errors may indicate the following problems: + + + + -EINVAL + + The input is not valid JSON, the input contains trailing content after the parsed + value (only for non-_continue variants), or a top-level type constraint + specified via SD_JSON_PARSE_MUST_BE_OBJECT or + SD_JSON_PARSE_MUST_BE_ARRAY was violated. + + + + -ENODATA + + The input string is empty or NULL. + + + + -ENOMEM + + Memory allocation failed. + + + + + + + + + History + sd_json_parse(), + sd_json_parse_continue(), + sd_json_parse_with_source(), + sd_json_parse_with_source_continue(), + sd_json_parse_file(), and + sd_json_parse_file_at() were added in version 257. + + + + See Also + + + systemd1 + sd-json3 + sd_json_variant_unref3 + sd_json_variant_get_source3 + sd_json_dispatch3 + + + diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 0f763f75e9e..bdbd44910bf 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -3,6 +3,7 @@ #include #include "sd-bus.h" +#include "sd-json.h" #include "alloc-util.h" #include "analyze-verify-util.h" @@ -2919,7 +2920,7 @@ int verb_security(int argc, char *argv[], uintptr_t _data, void *userdata) { unsigned line = 0, column = 0; if (arg_security_policy) { - r = sd_json_parse_file(/* f= */ NULL, arg_security_policy, /* flags= */ 0, &policy, &line, &column); + r = sd_json_parse_file(/* f= */ NULL, arg_security_policy, SD_JSON_PARSE_MUST_BE_OBJECT, &policy, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column); } else { @@ -2931,7 +2932,7 @@ int verb_security(int argc, char *argv[], uintptr_t _data, void *userdata) { return r; if (f) { - r = sd_json_parse_file(f, pp, /* flags= */ 0, &policy, &line, &column); + r = sd_json_parse_file(f, pp, SD_JSON_PARSE_MUST_BE_OBJECT, &policy, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column); } diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 0f655a4694d..f41d6fef310 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -829,7 +829,7 @@ static int print_info(FILE *file, sd_journal *j, bool need_space) { if (exe && pkgmeta_json) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = sd_json_parse(pkgmeta_json, 0, &v, NULL, NULL); + r = sd_json_parse(pkgmeta_json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) { _cleanup_free_ char *esc = cescape(pkgmeta_json); log_warning_errno(r, "json_parse on \"%s\" failed, ignoring: %m", strnull(esc)); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index 02ed4dd273c..a2804e033a1 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -162,7 +162,7 @@ _public_ int cryptsetup_token_validate( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c index 4c6e28500a3..16cf910fe6d 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -115,7 +115,7 @@ _public_ int cryptsetup_token_validate( sd_json_variant *w; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 933d18e2fd7..58dc37c5bfb 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -72,7 +72,7 @@ _public_ int cryptsetup_token_open_pin( if (usrptr) params = *(systemd_tpm2_plugin_params *)usrptr; - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m"); @@ -186,7 +186,7 @@ _public_ void cryptsetup_token_dump( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m"); @@ -275,7 +275,7 @@ _public_ int cryptsetup_token_validate( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c index 18b0e4f37f9..c6cfdcf6efe 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c @@ -97,7 +97,7 @@ int parse_luks2_fido2_data( assert(ret_cid_size); assert(ret_required); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return crypt_log_error_errno(cd, r, "Failed to parse JSON token data: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c index 9f11f81c4ac..723265479cc 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c @@ -246,7 +246,7 @@ int parse_luks2_pkcs11_data( assert(ret_encrypted_key); assert(ret_encrypted_key_size); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return r; diff --git a/src/home/homectl.c b/src/home/homectl.c index 507860cde62..db4e6639d5d 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -758,7 +758,7 @@ static int inspect_home(sd_bus *bus, const char *name) { if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); @@ -1159,7 +1159,11 @@ static int acquire_new_home_record(sd_json_variant *input, UserRecord **ret) { r = sd_json_parse_file( streq(arg_identity, "-") ? stdin : NULL, - streq(arg_identity, "-") ? "" : arg_identity, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + streq(arg_identity, "-") ? "" : arg_identity, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &v, + &line, + &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); } else @@ -1667,7 +1671,7 @@ static int register_home_one(sd_bus *bus, FILE *f, const char *path) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse_file(f, path, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(f, path, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse user record: %m", path, line, column); @@ -1785,7 +1789,11 @@ static int acquire_updated_home_record( r = sd_json_parse_file( streq(arg_identity, "-") ? stdin : NULL, - streq(arg_identity, "-") ? "" : arg_identity, SD_JSON_PARSE_SENSITIVE, &json, &line, &column); + streq(arg_identity, "-") ? "" : arg_identity, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &json, + &line, + &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); @@ -1822,7 +1830,12 @@ static int acquire_updated_home_record( if (incomplete) return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Lacking rights to acquire user record including privileged metadata, can't update record."); - r = sd_json_parse(text, SD_JSON_PARSE_SENSITIVE, &json, NULL, NULL); + r = sd_json_parse( + text, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &json, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); @@ -2569,7 +2582,7 @@ static int create_or_register_from_credentials(void) { /* f= */ NULL, fd, de->d_name, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &identity, &line, &column); @@ -5135,7 +5148,7 @@ static int fallback_shell(int argc, char *argv[]) { if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON identity: %m"); diff --git a/src/home/homed-bus.c b/src/home/homed-bus.c index c96ecf66205..f185e872955 100644 --- a/src/home/homed-bus.c +++ b/src/home/homed-bus.c @@ -24,7 +24,7 @@ int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *e if (r < 0) return r; - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON secret record at %u:%u: %m", line, column); @@ -57,7 +57,7 @@ int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, U if (r < 0) return r; - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON identity record at %u:%u: %m", line, column); diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 00b2e72f9fb..12e0eed2dae 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -589,7 +589,7 @@ static int home_parse_worker_stdout(int _fd, UserRecord **ret) { } unsigned line = 0, column = 0; - r = sd_json_parse_file(f, "stdout", SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(f, "stdout", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index 85c92192f48..c9d43982d01 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -406,9 +406,9 @@ static int manager_add_home_by_record( goto unlink_this_file; unsigned line = 0, column = 0; - r = sd_json_parse_file_at(NULL, dir_fd, fname, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file_at(/* f= */ NULL, dir_fd, fname, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) - return log_error_errno(r, "Failed to parse identity record at %s:%u%u: %m", fname, line, column); + return log_error_errno(r, "Failed to parse identity record at %s:%u:%u: %m", fname, line, column); if (sd_json_variant_is_blank_object(v)) goto unlink_this_file; diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 9f7af06a4e7..caa05db26f4 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -887,7 +887,7 @@ static int luks_validate_home_record( return log_error_errno(r, "Failed to read LUKS token %i: %m", token); unsigned line = 0, column = 0; - r = sd_json_parse(text, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse LUKS token JSON data %u:%u: %m", line, column); @@ -940,7 +940,7 @@ static int luks_validate_home_record( decrypted[decrypted_size] = 0; - r = sd_json_parse(decrypted, SD_JSON_PARSE_SENSITIVE, &rr, NULL, NULL); + r = sd_json_parse(decrypted, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &rr, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to parse decrypted JSON record, refusing."); diff --git a/src/home/homework.c b/src/home/homework.c index e796f125fb5..2efd3ddb608 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -571,7 +571,7 @@ static int read_identity_file(int root_fd, sd_json_variant **ret) { return log_oom(); unsigned line = 0, column = 0; - r = sd_json_parse_file(identity_file, ".identity", SD_JSON_PARSE_SENSITIVE, ret, &line, &column); + r = sd_json_parse_file(identity_file, ".identity", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, ret, &line, &column); if (r < 0) return log_error_errno(r, "[.identity:%u:%u] Failed to parse JSON data: %m", line, column); @@ -2025,7 +2025,7 @@ static int run(int argc, char *argv[]) { } unsigned line = 0, column = 0; - r = sd_json_parse_file(json_file, json_path, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file(json_file, json_path, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column); diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index c58a3433760..e8d7282cbf8 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -194,7 +194,7 @@ static int acquire_user_record( fresh_data = true; } - r = sd_json_parse(json, /* flags= */ 0, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to parse JSON user record: %m"); diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index d0ceceb1558..75a17a13aee 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -567,7 +567,7 @@ static int verb_show_status(int argc, char *argv[], uintptr_t _data, void *userd if (r < 0) return bus_log_parse_error(r); - r = sd_json_parse(text, 0, &v, NULL, NULL); + r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse JSON: %m"); diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index cbbad44eb1e..0f16c65630a 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -200,7 +200,7 @@ int oci_pull_new( return 0; } -static int pull_job_payload_as_json(PullJob *j, sd_json_variant **ret) { +static int pull_job_payload_as_json_object(PullJob *j, sd_json_variant **ret) { int r; assert(j); @@ -214,7 +214,7 @@ static int pull_job_payload_as_json(PullJob *j, sd_json_variant **ret) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse((char*) j->payload.iov_base, /* flags= */ 0, &v, &line, &column); + r = sd_json_parse((char*) j->payload.iov_base, SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse JSON at position %u:%u: %m", line, column); @@ -391,7 +391,7 @@ static int oci_pull_process_index(OciPull *i, PullJob *j) { * https://github.com/opencontainers/image-spec/blob/main/image-index.md */ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) return r; @@ -783,7 +783,7 @@ static int oci_pull_process_manifest(OciPull *i, PullJob *j) { * https://github.com/opencontainers/image-spec/blob/main/manifest.md */ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) return r; @@ -958,7 +958,7 @@ static int oci_pull_save_nspawn_settings(OciPull *i) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse((char*) i->config.iov_base, /* flags= */ 0, &v, &line, &column); + r = sd_json_parse((char*) i->config.iov_base, SD_JSON_PARSE_MUST_BE_OBJECT, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse JSON config data at position %u:%u: %m", line, column); @@ -1340,7 +1340,7 @@ static void oci_pull_job_on_finished_bearer_token(PullJob *j) { goto finish; } - r = pull_job_payload_as_json(j, &v); + r = pull_job_payload_as_json_object(j, &v); if (r < 0) goto finish; diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 5c24de4084f..0268bbf2c29 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -490,7 +490,7 @@ static int load_leases_file(int dir_fd, const char *path, SavedInfo *ret) { /* f= */ NULL, dir_fd, path, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* ret_column= */ NULL); diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 7829e118806..b959fe16286 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -3039,7 +3039,6 @@ static int json_parse_internal( int r; assert_return(input, -EINVAL); - assert_return(ret, -EINVAL); p = *input; @@ -3111,12 +3110,16 @@ static int json_parse_internal( break; case JSON_TOKEN_OBJECT_OPEN: - if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_FIRST_ELEMENT, EXPECT_ARRAY_NEXT_ELEMENT)) { r = -EINVAL; goto finish; } + if (n_stack == 1 && FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_ARRAY) && !FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_OBJECT)) { + r = -EINVAL; + goto finish; + } + if (!GREEDY_REALLOC(stack, n_stack+1)) { r = -ENOMEM; goto finish; @@ -3168,6 +3171,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && !FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_ARRAY) && FLAGS_SET(flags, SD_JSON_PARSE_MUST_BE_OBJECT)) { + r = -EINVAL; + goto finish; + } + if (!GREEDY_REALLOC(stack, n_stack+1)) { r = -ENOMEM; goto finish; @@ -3217,6 +3225,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_string(&add, string); if (r < 0) goto finish; @@ -3240,6 +3253,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_real(&add, value.real); if (r < 0) goto finish; @@ -3261,6 +3279,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_integer(&add, value.integer); if (r < 0) goto finish; @@ -3282,6 +3305,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_unsigned(&add, value.unsig); if (r < 0) goto finish; @@ -3303,6 +3331,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_boolean(&add, value.boolean); if (r < 0) goto finish; @@ -3324,6 +3357,11 @@ static int json_parse_internal( goto finish; } + if (n_stack == 1 && (flags & (SD_JSON_PARSE_MUST_BE_ARRAY|SD_JSON_PARSE_MUST_BE_OBJECT)) != 0) { + r = -EINVAL; + goto finish; + } + r = sd_json_variant_new_null(&add); if (r < 0) goto finish; @@ -3365,7 +3403,8 @@ done: assert(n_stack == 1); assert(stack[0].n_elements == 1); - *ret = sd_json_variant_ref(stack[0].elements[0]); + if (ret) + *ret = sd_json_variant_ref(stack[0].elements[0]); *input = p; r = 0; diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index 7ee85e97895..c1ffaedfc80 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -1009,14 +1009,14 @@ static int varlink_parse_message(sd_varlink *v) { sz = e - begin + 1; - r = sd_json_parse(begin, 0, &v->current, NULL, NULL); + r = sd_json_parse(begin, SD_JSON_PARSE_MUST_BE_OBJECT, &v->current, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (v->input_sensitive) explicit_bzero_safe(begin, sz); if (r < 0) { /* If we encounter a parse failure flush all data. We cannot possibly recover from this, * hence drop all buffered data now. */ v->input_buffer_index = v->input_buffer_size = v->input_buffer_unscanned = 0; - return varlink_log_errno(v, r, "Failed to parse JSON: %m"); + return varlink_log_errno(v, r, "Failed to parse JSON object: %m"); } if (v->input_sensitive) { diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index f7aa6f9b8f6..ec862e7d4fd 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -205,7 +205,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; /* Parse cached record */ - r = sd_json_parse(json, SD_JSON_PARSE_SENSITIVE, &v, NULL, NULL); + r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to parse JSON user record: %m"); diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 2f37d583819..d632bb62f55 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -820,13 +820,9 @@ static int build_policy_digest(bool sign) { assert(!strv_isempty(arg_phase)); if (arg_append) { - r = sd_json_parse_file(NULL, arg_append, 0, &v, NULL, NULL); + r = sd_json_parse_file(/* f= */ NULL, arg_append, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); if (r < 0) - return log_error_errno(r, "Failed to parse '%s': %m", arg_append); - - if (!sd_json_variant_is_object(v)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "File '%s' is not a valid JSON object, refusing.", arg_append); + return log_error_errno(r, "Failed to parse JSON object '%s': %m", arg_append); } /* When signing/building digest we only support JSON output */ diff --git a/src/systemd/sd-json.h b/src/systemd/sd-json.h index 359149ef9e0..6a197754909 100644 --- a/src/systemd/sd-json.h +++ b/src/systemd/sd-json.h @@ -181,7 +181,9 @@ int sd_json_variant_sort(sd_json_variant **v); int sd_json_variant_normalize(sd_json_variant **v); __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_json_parse_flags_t) { - SD_JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */ + SD_JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */ + SD_JSON_PARSE_MUST_BE_OBJECT = 1 << 1, /* refuse parsing if top-level is not an object */ + SD_JSON_PARSE_MUST_BE_ARRAY = 1 << 2, /* refuse parsing if top-level is not an array */ _SD_ENUM_FORCE_S64(JSON_PARSE_FLAGS) } sd_json_parse_flags_t; diff --git a/src/test/test-json.c b/src/test/test-json.c index 679152fd955..4994d42abc4 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -1588,4 +1588,46 @@ TEST(json_variant_compare) { test_json_variant_compare_one("{\"a\":\"b\",\"b\":\"c\"}", "{\"a\":\"b\"}", 1); } +TEST(must_be) { + ASSERT_OK(sd_json_parse("null", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("null", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("true", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("true", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("\"foo\"", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("\"foo\"", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("4711", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("4711", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("-4711", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4711", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("-4.5", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(sd_json_parse("-4.5", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + + ASSERT_OK(sd_json_parse("{}", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_OK(sd_json_parse("{}", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + + ASSERT_OK(sd_json_parse("[]", /* flags= */ 0, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_OBJECT, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_OK(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(sd_json_parse("[]", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_MUST_BE_ARRAY, /* ret= */ NULL, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index b0d7a06941e..8da35172c54 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1260,7 +1260,7 @@ static int load_credential_one( _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned line = 0, column = 0; - r = sd_json_parse_file_at(NULL, credential_dir_fd, name, SD_JSON_PARSE_SENSITIVE, &v, &line, &column); + r = sd_json_parse_file_at(/* f= */ NULL, credential_dir_fd, name, SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse credential '%s' as JSON at %u:%u: %m", name, line, column); @@ -1844,7 +1844,7 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; const char *fn = streq(optarg, "-") ? NULL : optarg; unsigned line = 0; - r = sd_json_parse_file(fn ? NULL : stdin, fn ?: "", SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL); + r = sd_json_parse_file(fn ? NULL : stdin, fn ?: "", SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL); if (r < 0) return log_syntax(/* unit= */ NULL, LOG_ERR, fn ?: "", line, r, "JSON parse failure."); diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 5048f433b17..775a481ae8e 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -682,14 +682,14 @@ static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) { source = ""; /* is correct, as dispatch_verb() shifts arguments by one for the verb. */ - r = sd_json_parse_with_source(parameter, source, 0, &jp, &line, &column); + r = sd_json_parse_with_source(parameter, source, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, &line, &column); } else { if (isatty_safe(STDIN_FILENO) && !arg_quiet) log_notice("Expecting method call parameter JSON object on standard input. (Provide empty string or {} for no parameters.)"); source = ""; - r = sd_json_parse_file_at(stdin, AT_FDCWD, source, 0, &jp, &line, &column); + r = sd_json_parse_file_at(stdin, AT_FDCWD, source, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, &line, &column); } if (r < 0 && r != -ENODATA) return log_error_errno(r, "Failed to parse parameters at %s:%u:%u: %m", source, line, column); diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 3f65fab2cc5..c6e258c50af 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -253,7 +253,7 @@ static int load_firmware_data(const char *path, FirmwareData **ret) { r = sd_json_parse_file( /* f= */ NULL, path, - /* flags= */ 0, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, &json, /* reterr_line= */ NULL, /* ret_column= */ NULL);