Reject embedded NUL bytes in module import paths
jq accepts embedded NUL bytes at the language level but resolves module import paths through NUL-terminated C strings, so the path validated by policy or audit code could differ from the on-disk path jq actually opens. Pass jv through gen_import so the AST preserves the original bytes, and reject embedded NULs in validate_relpath. Fixes CVE-2026-43895.
This commit is contained in:
1 parent
9761ceb7d6
commit
9d223f153c
6 files changed
+314
-309
No files matched your search
+8
-4
@@ -525,13 +525,17 @@ jv block_module_meta(block b) {
|
||||
return jv_null();
|
||||
}
|
||||
|
||||
block gen_import(const char* name, const char* as, int is_data) {
|
||||
block gen_import(jv name, jv as, int is_data) {
|
||||
assert(jv_get_kind(name) == JV_KIND_STRING);
|
||||
assert(!jv_is_valid(as) || jv_get_kind(as) == JV_KIND_STRING);
|
||||
inst* i = inst_new(DEPS);
|
||||
jv meta = jv_object();
|
||||
if (as != NULL)
|
||||
meta = jv_object_set(meta, jv_string("as"), jv_string(as));
|
||||
if (jv_is_valid(as))
|
||||
meta = jv_object_set(meta, jv_string("as"), as);
|
||||
else
|
||||
jv_free(as);
|
||||
meta = jv_object_set(meta, jv_string("is_data"), is_data ? jv_true() : jv_false());
|
||||
meta = jv_object_set(meta, jv_string("relpath"), jv_string(name));
|
||||
meta = jv_object_set(meta, jv_string("relpath"), name);
|
||||
i->imm.constant = meta;
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ block gen_op_pushk_under(jv constant);
|
||||
|
||||
block gen_module(block metadata);
|
||||
jv block_module_meta(block b);
|
||||
block gen_import(const char* name, const char *as, int is_data);
|
||||
block gen_import(jv name, jv as, int is_data);
|
||||
block gen_import_meta(block import, block metadata);
|
||||
block gen_function(const char* name, block formals, block body);
|
||||
block gen_param_regular(const char* name);
|
||||
|
||||
+5
-1
@@ -93,6 +93,10 @@ static jv build_lib_search_chain(jq_state *jq, jv search_path, jv jq_origin, jv
|
||||
// in between).
|
||||
static jv validate_relpath(jv name) {
|
||||
const char *s = jv_string_value(name);
|
||||
if (strlen(s) != (size_t)jv_string_length_bytes(jv_copy(name))) {
|
||||
jv_free(name);
|
||||
return jv_invalid_with_msg(jv_string("Module path contains a NUL byte"));
|
||||
}
|
||||
if (strchr(s, '\\')) {
|
||||
jv res = jv_invalid_with_msg(jv_string_fmt("Modules must be named by relative paths using '/', not '\\' (%s)", s));
|
||||
jv_free(name);
|
||||
@@ -425,7 +429,7 @@ int load_program(jq_state *jq, struct locfile* src, block *out_block) {
|
||||
jv home = get_home();
|
||||
if (jv_is_valid(home)) {
|
||||
/* Import ~/.jq as a library named "" found in $HOME or %USERPROFILE% */
|
||||
block import = gen_import_meta(gen_import("", NULL, 0),
|
||||
block import = gen_import_meta(gen_import(jv_string(""), jv_invalid(), 0),
|
||||
gen_const(JV_OBJECT(
|
||||
jv_string("optional"), jv_true(),
|
||||
jv_string("search"), home)));
|
||||
|
||||
Generated
+280
-290
File diff suppressed because it is too large.
Load diff
+3
-13
@@ -439,26 +439,16 @@ ImportWhat Query ';' {
|
||||
|
||||
ImportWhat:
|
||||
"import" ImportFrom "as" BINDING {
|
||||
jv v = block_const($2);
|
||||
// XXX Make gen_import take only blocks and the int is_data so we
|
||||
// don't have to free so much stuff here
|
||||
$$ = gen_import(jv_string_value(v), jv_string_value($4), 1);
|
||||
$$ = gen_import(block_const($2), $4, 1);
|
||||
block_free($2);
|
||||
jv_free($4);
|
||||
jv_free(v);
|
||||
} |
|
||||
"import" ImportFrom "as" IDENT {
|
||||
jv v = block_const($2);
|
||||
$$ = gen_import(jv_string_value(v), jv_string_value($4), 0);
|
||||
$$ = gen_import(block_const($2), $4, 0);
|
||||
block_free($2);
|
||||
jv_free($4);
|
||||
jv_free(v);
|
||||
} |
|
||||
"include" ImportFrom {
|
||||
jv v = block_const($2);
|
||||
$$ = gen_import(jv_string_value(v), NULL, 0);
|
||||
$$ = gen_import(block_const($2), jv_invalid(), 0);
|
||||
block_free($2);
|
||||
jv_free(v);
|
||||
}
|
||||
|
||||
ImportFrom:
|
||||
|
||||
@@ -893,4 +893,21 @@ if echo '42' | $JQ -f "$d/nul_prog.jq" >/dev/null 2>/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# CVE-2026-43895: No NUL bytes in module/data import paths
|
||||
printf 'import "a\\u0000b" as $x; .' > "$d/nul_import.jq"
|
||||
if $JQ -nf "$d/nul_import.jq" >/dev/null 2>/dev/null; then
|
||||
printf 'Error expected for import path with NUL bytes\n' 1>&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'include "a\\u0000b"; .' > "$d/nul_include.jq"
|
||||
if $JQ -nf "$d/nul_include.jq" >/dev/null 2>/dev/null; then
|
||||
printf 'Error expected for include path with NUL bytes\n' 1>&2
|
||||
exit 1
|
||||
fi
|
||||
printf '"a\\u0000b" | modulemeta' > "$d/nul_modulemeta.jq"
|
||||
if $JQ -nf "$d/nul_modulemeta.jq" >/dev/null 2>/dev/null; then
|
||||
printf 'Error expected for modulemeta with NUL bytes\n' 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in new issue
Block a user