Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ TESTS = tests/test-1 \
tests/test-13 \
tests/test-14 \
tests/test-15 \
tests/test-16
tests/test-16 \
tests/test-17

noinst_PROGRAMS = src/ocispec/validate $(TESTS)

Expand Down
36 changes: 29 additions & 7 deletions src/ocispec/json_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,31 @@ safe_strdup (const char *src)
return dst;
}

/* Return a copy of the JSON string VAL, or NULL with *ERR set. A JSON string
can contain a NUL character (\u0000) but a C string cannot, so reject it
rather than silently truncating the value at that point. */
char *
dup_json_string (json_object *val, const char *key, parser_error *err)
{
const char *str = json_object_get_string (val);
char *ret;

if (str == NULL)
str = "";
else if (json_object_is_type (val, json_type_string)
&& strlen (str) != (size_t) json_object_get_string_len (val))
{
if (*err == NULL && asprintf (err, "Invalid NUL character in value for key '%s'", key) < 0)
*err = strdup ("error allocating memory");
return NULL;
}

ret = strdup (str);
if (ret == NULL && *err == NULL)
*err = strdup ("error allocating memory");
return ret;
}

void *
safe_malloc (size_t size)
{
Expand Down Expand Up @@ -1243,7 +1268,6 @@ make_json_map_int_string (json_object *src, const struct parser_context *ctx, pa

if (srcval != NULL)
{
const char *str;
if (! json_object_is_type (srcval, json_type_string))
{
if (*err == NULL && asprintf (err, "Invalid value with type 'string' for key '%s'", srckey) < 0)
Expand All @@ -1252,8 +1276,9 @@ make_json_map_int_string (json_object *src, const struct parser_context *ctx, pa
}
return NULL;
}
str = json_object_get_string (srcval);
ret->values[i] = strdup (str ? str : "");
ret->values[i] = dup_json_string (srcval, srckey, err);
if (ret->values[i] == NULL)
return NULL;
}
i++;
}
Expand Down Expand Up @@ -1886,7 +1911,6 @@ make_json_map_string_string (json_object *src, const struct parser_context *ctx,
}
if (srcval != NULL)
{
const char *str;
if (! json_object_is_type (srcval, json_type_string))
{
if (*err == NULL && asprintf (err, "Invalid value with type 'string' for key '%s'", srckey) < 0)
Expand All @@ -1896,9 +1920,7 @@ make_json_map_string_string (json_object *src, const struct parser_context *ctx,
return NULL;
}

str = json_object_get_string (srcval);

ret->values[i] = strdup (str ? str : "");
ret->values[i] = dup_json_string (srcval, srckey, err);
if (ret->values[i] == NULL)
{
return NULL;
Expand Down
2 changes: 2 additions & 0 deletions src/ocispec/json_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ json_object *get_val (json_object *tree, const char *name, int type);

char *safe_strdup (const char *src);

char *dup_json_string (json_object *val, const char *key, parser_error *err);

void *safe_malloc (size_t size);

int common_safe_double (const char *numstr, double *converted);
Expand Down
3 changes: 1 addition & 2 deletions src/ocispec/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,7 @@ def emit_read_value(self, c_file, src, dest, keyname, obj_typename, level=1):
{json_api.VAL_TYPE} val = {src};
if (val != NULL)
{{
const char *str = {json_api.get_string('val')};
{dest} = strdup (str ? str : "");
{dest} = dup_json_string (val, "{keyname}", err);
if ({dest} == NULL)
return NULL;
}}
Expand Down
101 changes: 101 additions & 0 deletions tests/test-17.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* Copyright (C) 2026 Kir Kolyshkin <kolyshkin@gmail.com>

libocispec is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

libocispec is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with libocispec. If not, see <http://www.gnu.org/licenses/>.

*/

/* Test that a JSON string containing a NUL character is rejected.

JSON allows \u0000 inside a string, but the parsed value is stored as a C
string, where the first NUL ends it. Unless rejected, the rest of the
value is silently dropped -- a mount option "nosuid\u0000,noexec" used to
become just "nosuid", with no error whatsoever. */

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ocispec/runtime_spec_schema_config_schema.h"

static const char *const configs[] = {
/* String. */
"{\"ociVersion\": \"1.0.0\", \"root\": {\"path\": \"rootfs\\u0000/etc\"}}",

/* String inside an array of objects. */
"{\"ociVersion\": \"1.0.0\", \"mounts\": [{\"destination\": \"/proc\\u0000/sys\", \"type\": \"proc\"}]}",

/* Array of strings. */
"{\"ociVersion\": \"1.0.0\", \"mounts\": [{\"destination\": \"/sys/fs/cgroup\", \"type\": \"cgroup2\","
" \"options\": [\"nosuid\\u0000,noexec\"]}]}",

"{\"ociVersion\": \"1.0.0\", \"process\": {\"cwd\": \"/\", \"args\": [\"/bin/sh\", \"-c\\u0000\"]}}",

/* Map of strings. */
"{\"ociVersion\": \"1.0.0\", \"annotations\": {\"key\": \"value\\u0000more\"}}",

/* NUL as the very first character. */
"{\"ociVersion\": \"1.0.0\", \"hostname\": \"\\u0000\"}",
};

int
main ()
{
runtime_spec_schema_config_schema *config = NULL;
parser_error err = NULL;
size_t i;
int ret = 0;

for (i = 0; i < sizeof (configs) / sizeof (configs[0]); i++)
{
config = runtime_spec_schema_config_schema_parse_data (configs[i], 0, &err);
if (config != NULL)
{
printf ("config #%zu: expected the parse to fail: %s\n", i, configs[i]);
free_runtime_spec_schema_config_schema (config);
free (err);
err = NULL;
ret = 1;
continue;
}

if (err == NULL)
{
printf ("config #%zu: parse failed without setting an error: %s\n", i, configs[i]);
ret = 1;
continue;
}

printf ("config #%zu rejected: %s\n", i, err);
free (err);
err = NULL;
}

/* Other escaped characters are fine. */
config = runtime_spec_schema_config_schema_parse_data (
"{\"ociVersion\": \"1.0.0\", \"hostname\": \"a\\u0001b\\u00e9\"}", 0, &err);
if (config == NULL)
{
printf ("valid config rejected: %s\n", err ? err : "(no error)");
free (err);
return 1;
}
if (strcmp (config->hostname, "a\001b\303\251") != 0)
{
printf ("unexpected hostname: %s\n", config->hostname);
ret = 1;
}
free_runtime_spec_schema_config_schema (config);

return ret;
}
Loading