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)
This commit is contained in:
Lennart Poettering
2026-03-20 15:34:56 +01:00
committed by Daan De Meyer
parent bc6380ae07
commit 2c74a91cb8
28 changed files with 405 additions and 53 deletions
+11
View File
@@ -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'],
+248
View File
@@ -0,0 +1,248 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="sd_json_parse" xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>sd_json_parse</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>sd_json_parse</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>sd_json_parse</refname>
<refname>sd_json_parse_continue</refname>
<refname>sd_json_parse_with_source</refname>
<refname>sd_json_parse_with_source_continue</refname>
<refname>sd_json_parse_file</refname>
<refname>sd_json_parse_file_at</refname>
<refname>SD_JSON_PARSE_SENSITIVE</refname>
<refname>SD_JSON_PARSE_MUST_BE_OBJECT</refname>
<refname>SD_JSON_PARSE_MUST_BE_ARRAY</refname>
<refpurpose>Parse JSON strings and files into JSON variant objects</refpurpose>
</refnamediv>
<refsynopsisdiv>
<funcsynopsis>
<funcsynopsisinfo>#include &lt;systemd/sd-json.h&gt;</funcsynopsisinfo>
<funcprototype>
<funcdef>int <function>sd_json_parse</function></funcdef>
<paramdef>const char *<parameter>string</parameter></paramdef>
<paramdef>sd_json_parse_flags_t <parameter>flags</parameter></paramdef>
<paramdef>sd_json_variant **<parameter>ret</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_line</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_column</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_json_parse_continue</function></funcdef>
<paramdef>const char **<parameter>p</parameter></paramdef>
<paramdef>sd_json_parse_flags_t <parameter>flags</parameter></paramdef>
<paramdef>sd_json_variant **<parameter>ret</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_line</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_column</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_json_parse_with_source</function></funcdef>
<paramdef>const char *<parameter>string</parameter></paramdef>
<paramdef>const char *<parameter>source</parameter></paramdef>
<paramdef>sd_json_parse_flags_t <parameter>flags</parameter></paramdef>
<paramdef>sd_json_variant **<parameter>ret</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_line</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_column</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_json_parse_with_source_continue</function></funcdef>
<paramdef>const char **<parameter>p</parameter></paramdef>
<paramdef>const char *<parameter>source</parameter></paramdef>
<paramdef>sd_json_parse_flags_t <parameter>flags</parameter></paramdef>
<paramdef>sd_json_variant **<parameter>ret</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_line</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_column</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_json_parse_file</function></funcdef>
<paramdef>FILE *<parameter>f</parameter></paramdef>
<paramdef>const char *<parameter>path</parameter></paramdef>
<paramdef>sd_json_parse_flags_t <parameter>flags</parameter></paramdef>
<paramdef>sd_json_variant **<parameter>ret</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_line</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_column</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_json_parse_file_at</function></funcdef>
<paramdef>FILE *<parameter>f</parameter></paramdef>
<paramdef>int <parameter>dir_fd</parameter></paramdef>
<paramdef>const char *<parameter>path</parameter></paramdef>
<paramdef>sd_json_parse_flags_t <parameter>flags</parameter></paramdef>
<paramdef>sd_json_variant **<parameter>ret</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_line</parameter></paramdef>
<paramdef>unsigned *<parameter>reterr_column</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><function>sd_json_parse()</function> parses the JSON string in <parameter>string</parameter> and
returns the resulting JSON variant object in <parameter>ret</parameter>. 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.</para>
<para>If parsing fails, the <parameter>reterr_line</parameter> and <parameter>reterr_column</parameter>
arguments are set to the line and column (both one-based) where the parse error occurred. One or both
may be passed as <constant>NULL</constant> if the caller is not interested in error location
information. On success, the return value is non-negative and <parameter>ret</parameter> is set to a
newly allocated JSON variant object (which must be freed with
<citerefentry><refentrytitle>sd_json_variant_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>
when no longer needed). <parameter>ret</parameter> may be passed as <constant>NULL</constant>, in which
case the input is validated but no object is returned.</para>
<para><function>sd_json_parse_continue()</function> is similar, but is intended for parsing a sequence of
concatenated JSON values from a single input string. Instead of taking a <type>const char *</type> string
directly, it takes a pointer to a <type>const char *</type> 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 <function>sd_json_parse()</function>, trailing content after the first JSON value is not
considered an error — it is expected to be the beginning of the next value.</para>
<para><function>sd_json_parse_with_source()</function> and
<function>sd_json_parse_with_source_continue()</function> are similar to
<function>sd_json_parse()</function> and <function>sd_json_parse_continue()</function>, respectively, but
take an additional <parameter>source</parameter> 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
<citerefentry><refentrytitle>sd_json_variant_get_source</refentrytitle><manvolnum>3</manvolnum></citerefentry>. If
<parameter>source</parameter> is <constant>NULL</constant>, no source information is
attached. <function>sd_json_parse()</function> and <function>sd_json_parse_continue()</function> are
equivalent to calling their <function>_with_source</function> counterparts with
<parameter>source</parameter> set to <constant>NULL</constant>.</para>
<para><function>sd_json_parse_file()</function> reads and parses a JSON value from a file. If the
<parameter>f</parameter> argument is non-<constant>NULL</constant>, the JSON text is read from the
specified <type>FILE</type> stream. If <parameter>f</parameter> is <constant>NULL</constant>, the file
indicated by <parameter>path</parameter> is opened and read instead. The <parameter>path</parameter>
argument serves a dual purpose: it is both used for opening the file (if <parameter>f</parameter> is
<constant>NULL</constant>) and recorded as source information in the resulting JSON variant (see
above).</para>
<para><function>sd_json_parse_file_at()</function> is similar to
<function>sd_json_parse_file()</function>, but takes an additional <parameter>dir_fd</parameter> argument
which specifies a file descriptor referring to the directory to resolve relative paths specified in
<parameter>path</parameter> against. If set to <constant>AT_FDCWD</constant>, relative paths are resolved
against the current working directory, which is the default behaviour of
<function>sd_json_parse_file()</function>.</para>
<para>The <parameter>flags</parameter> argument is a bitmask of zero or more of the following
flags:</para>
<variablelist>
<varlistentry>
<term><constant>SD_JSON_PARSE_SENSITIVE</constant></term>
<listitem><para>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
<citerefentry><refentrytitle>sd_json_variant_sensitive</refentrytitle><manvolnum>3</manvolnum></citerefentry>
for details.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>SD_JSON_PARSE_MUST_BE_OBJECT</constant></term>
<listitem><para>Requires that the top-level JSON value be a JSON object (i.e. <literal>{…}</literal>).
If the top-level value is an array, string, number, boolean, or null, parsing fails with
<constant>-EINVAL</constant>.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><constant>SD_JSON_PARSE_MUST_BE_ARRAY</constant></term>
<listitem><para>Requires that the top-level JSON value be a JSON array (i.e. <literal>[…]</literal>).
If the top-level value is an object, string, number, boolean, or null, parsing fails with
<constant>-EINVAL</constant>.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
</variablelist>
<para>If both <constant>SD_JSON_PARSE_MUST_BE_OBJECT</constant> and
<constant>SD_JSON_PARSE_MUST_BE_ARRAY</constant> are set, both objects and arrays are accepted, but
non-container values (strings, numbers, booleans, null) are still refused.</para>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>On success, these functions return 0. On failure, they return a negative errno-style error
code.</para>
<refsect2>
<title>Errors</title>
<para>Returned errors may indicate the following problems:</para>
<variablelist>
<varlistentry>
<term><constant>-EINVAL</constant></term>
<listitem><para>The input is not valid JSON, the input contains trailing content after the parsed
value (only for non-<function>_continue</function> variants), or a top-level type constraint
specified via <constant>SD_JSON_PARSE_MUST_BE_OBJECT</constant> or
<constant>SD_JSON_PARSE_MUST_BE_ARRAY</constant> was violated.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ENODATA</constant></term>
<listitem><para>The input string is empty or <constant>NULL</constant>.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-ENOMEM</constant></term>
<listitem><para>Memory allocation failed.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
</refsect1>
<xi:include href="libsystemd-pkgconfig.xml" />
<refsect1>
<title>History</title>
<para><function>sd_json_parse()</function>,
<function>sd_json_parse_continue()</function>,
<function>sd_json_parse_with_source()</function>,
<function>sd_json_parse_with_source_continue()</function>,
<function>sd_json_parse_file()</function>, and
<function>sd_json_parse_file_at()</function> were added in version 257.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd-json</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd_json_variant_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd_json_variant_get_source</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd_json_dispatch</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+3 -2
View File
@@ -3,6 +3,7 @@
#include <linux/capability.h>
#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);
}
+1 -1
View File
@@ -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));
@@ -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.");
@@ -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.");
@@ -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");
@@ -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");
@@ -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;
+20 -7
View File
@@ -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, "-") ? "<stdin>" : arg_identity, SD_JSON_PARSE_SENSITIVE, &v, &line, &column);
streq(arg_identity, "-") ? "<stdin>" : 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, "-") ? "<stdin>" : arg_identity, SD_JSON_PARSE_SENSITIVE, &json, &line, &column);
streq(arg_identity, "-") ? "<stdin>" : 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");
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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.");
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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");
+1 -1
View File
@@ -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");
+6 -6
View File
@@ -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;
@@ -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);
+42 -3
View File
@@ -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;
+2 -2
View File
@@ -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) {
+1 -1
View File
@@ -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");
+2 -6
View File
@@ -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 */
+3 -1
View File
@@ -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;
+42
View File
@@ -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);
+2 -2
View File
@@ -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 ?: "<stdin>", SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL);
r = sd_json_parse_file(fn ? NULL : stdin, fn ?: "<stdin>", 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 ?: "<stdin>", line, r, "JSON parse failure.");
+2 -2
View File
@@ -682,14 +682,14 @@ static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) {
source = "<argv[4]>";
/* <argv[4]> 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 = "<stdin>";
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);
+1 -1
View File
@@ -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);