Skip to content

Commit

Permalink
Add --sandbox flag to prevent dynamic loading of other files/data.
Browse files Browse the repository at this point in the history
Some use cases for `jq` may involve accepting untrusted input.
See discussion in jqlang#1361 for some security considerations that
may be relevant for those use cases.

This commit adds a `--sandbox` flag which is meant to mitigate
one category of security issue with untrusted input:
features of jq which are meant to let the jq filter access
files/data other than the direct input data given to the CLI.

Specifically, the new `--sandbox` flag blocks the implicit
loading of `$HOME/.jq`, and also blocks the use of
`import` and `include` for loading other `jq` files.

If other features are added to `jq` in the future which allow
for reading files/data as part of the filter syntax, it is
intended that the `--sandbox` flag would also gate access to those.
  • Loading branch information
jemc committed Apr 12, 2024
1 parent 6408338 commit 45ac611
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 9 deletions.
6 changes: 6 additions & 0 deletions docs/content/manual/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,12 @@ sections:
Another way to set the exit status is with the `halt_error`
builtin function.
* `--sandbox`:
Prevent the use of modules (`import`/`include`) or any other file
operations that would allow the filter code to access data other
than the input data that is explicitly specified in the invocation.
* `--binary` / `-b`:
Windows users using WSL, MSYS2, or Cygwin, should use this option
Expand Down
8 changes: 7 additions & 1 deletion jq.1.prebuilt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct jq_state {
unsigned next_label;

int halted;
int sandbox;
jv exit_code;
jv error_message;

Expand Down Expand Up @@ -1066,6 +1067,7 @@ jq_state *jq_init(void) {
jq->curr_frame = 0;
jq->error = jv_null();

jq->sandbox = 0;
jq->halted = 0;
jq->exit_code = jv_invalid();
jq->error_message = jv_invalid();
Expand Down Expand Up @@ -1321,6 +1323,14 @@ void jq_get_stderr_cb(jq_state *jq, jq_msg_cb *cb, void **data) {
*data = jq->stderr_cb_data;
}

void jq_set_sandbox(jq_state *jq) {
jq->sandbox = 1;
}

int jq_is_sandbox(jq_state *jq) {
return jq->sandbox;
}

void
jq_halt(jq_state *jq, jv exit_code, jv error_message)
{
Expand Down
2 changes: 2 additions & 0 deletions src/jq.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ void jq_start(jq_state *, jv value, int);
jv jq_next(jq_state *);
void jq_teardown(jq_state **);

void jq_set_sandbox(jq_state *);
int jq_is_sandbox(jq_state *);
void jq_halt(jq_state *, jv, jv);
int jq_halted(jq_state *);
jv jq_get_exit_code(jq_state *);
Expand Down
27 changes: 19 additions & 8 deletions src/linker.c
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,15 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
i--;
jv dep = jv_array_get(jv_copy(deps), i);

// Loading dependencies is not allowed when running in sandbox mode.
if (jq_is_sandbox(jq)) {
jq_report_error(jq, jv_string("jq: error: Loading dependencies (with import or include) is not allowed in sandbox mode"));
jv_free(deps);
jv_free(jq_origin);
jv_free(lib_origin);
return 1;
}

const char *as_str = NULL;
int is_data = jv_get_kind(jv_object_get(jv_copy(dep), jv_string("is_data"))) == JV_KIND_TRUE;
int raw = 0;
Expand Down Expand Up @@ -420,14 +429,16 @@ int load_program(jq_state *jq, struct locfile* src, block *out_block) {
return 1;
}

char* home = getenv("HOME");
if (home) { // silently ignore no $HOME
/* Import ~/.jq as a library named "" found in $HOME */
block import = gen_import_meta(gen_import("", NULL, 0),
gen_const(JV_OBJECT(
jv_string("optional"), jv_true(),
jv_string("search"), jv_string(home))));
program = BLOCK(import, program);
if (!jq_is_sandbox(jq)) {
char* home = getenv("HOME");
if (home) { // silently ignore no $HOME
/* Import ~/.jq as a library named "" found in $HOME */
block import = gen_import_meta(gen_import("", NULL, 0),
gen_const(JV_OBJECT(
jv_string("optional"), jv_true(),
jv_string("search"), jv_string(home))));
program = BLOCK(import, program);
}
}

nerrors = process_dependencies(jq, jq_get_jq_origin(jq), jq_get_prog_origin(jq), &program, &lib_state);
Expand Down
5 changes: 5 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ static void usage(int code, int keep_it_short) {
" --jsonargs consume remaining arguments as positional\n"
" JSON values;\n"
" -e, --exit-status set exit status code based on the output;\n"
" --sandbox prevent dynamic access to other files/data;\n"
#ifdef WIN32
" -b, --binary open input/output streams in binary mode;\n"
#endif
Expand Down Expand Up @@ -475,6 +476,10 @@ int main(int argc, char* argv[]) {
parser_flags |= JV_PARSE_STREAMING | JV_PARSE_STREAM_ERRORS;
continue;
}
if (isoption(argv[i], 0, "sandbox", &short_opts)) {
jq_set_sandbox(jq);
continue;
}
if (isoption(argv[i], 'e', "exit-status", &short_opts)) {
options |= EXIT_STATUS;
if (!short_opts) continue;
Expand Down
15 changes: 15 additions & 0 deletions tests/shtest
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,21 @@ if ! $VALGRIND $Q $JQ -L tests/modules -ne 'import "test_bind_order" as check; c
exit 1
fi

if HOME="$mods/home1" $VALGRIND $Q $JQ --sandbox -nr fg; then
echo "home module was loaded when it should have been prevented by sandbox flag" 1>&2
exit 1
fi

if HOME="$mods/home2" $VALGRIND $Q $JQ --sandbox -n 'include "g"; empty'; then
echo "module was included when it should have been prevented by sandbox flag" 1>&2
exit 1
fi

if $VALGRIND $Q $JQ -L ./tests/modules --sandbox -n 'import "a" as a; empty'; then
echo "module was imported when it should have been prevented by sandbox flag" 1>&2
exit 1
fi

## Halt

if ! $VALGRIND $Q $JQ -n halt; then
Expand Down

0 comments on commit 45ac611

Please sign in to comment.