Skip to content

Commit 45ac611

Browse files
committed
Add --sandbox flag to prevent dynamic loading of other files/data.
Some use cases for `jq` may involve accepting untrusted input. See discussion in #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.
1 parent 6408338 commit 45ac611

File tree

7 files changed

+64
-9
lines changed

7 files changed

+64
-9
lines changed

docs/content/manual/manual.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,12 @@ sections:
291291
Another way to set the exit status is with the `halt_error`
292292
builtin function.
293293
294+
* `--sandbox`:
295+
296+
Prevent the use of modules (`import`/`include`) or any other file
297+
operations that would allow the filter code to access data other
298+
than the input data that is explicitly specified in the invocation.
299+
294300
* `--binary` / `-b`:
295301
296302
Windows users using WSL, MSYS2, or Cygwin, should use this option

jq.1.prebuilt

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/execute.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ struct jq_state {
4141
unsigned next_label;
4242

4343
int halted;
44+
int sandbox;
4445
jv exit_code;
4546
jv error_message;
4647

@@ -1066,6 +1067,7 @@ jq_state *jq_init(void) {
10661067
jq->curr_frame = 0;
10671068
jq->error = jv_null();
10681069

1070+
jq->sandbox = 0;
10691071
jq->halted = 0;
10701072
jq->exit_code = jv_invalid();
10711073
jq->error_message = jv_invalid();
@@ -1321,6 +1323,14 @@ void jq_get_stderr_cb(jq_state *jq, jq_msg_cb *cb, void **data) {
13211323
*data = jq->stderr_cb_data;
13221324
}
13231325

1326+
void jq_set_sandbox(jq_state *jq) {
1327+
jq->sandbox = 1;
1328+
}
1329+
1330+
int jq_is_sandbox(jq_state *jq) {
1331+
return jq->sandbox;
1332+
}
1333+
13241334
void
13251335
jq_halt(jq_state *jq, jv exit_code, jv error_message)
13261336
{

src/jq.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ void jq_start(jq_state *, jv value, int);
3030
jv jq_next(jq_state *);
3131
void jq_teardown(jq_state **);
3232

33+
void jq_set_sandbox(jq_state *);
34+
int jq_is_sandbox(jq_state *);
3335
void jq_halt(jq_state *, jv, jv);
3436
int jq_halted(jq_state *);
3537
jv jq_get_exit_code(jq_state *);

src/linker.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,15 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
251251
i--;
252252
jv dep = jv_array_get(jv_copy(deps), i);
253253

254+
// Loading dependencies is not allowed when running in sandbox mode.
255+
if (jq_is_sandbox(jq)) {
256+
jq_report_error(jq, jv_string("jq: error: Loading dependencies (with import or include) is not allowed in sandbox mode"));
257+
jv_free(deps);
258+
jv_free(jq_origin);
259+
jv_free(lib_origin);
260+
return 1;
261+
}
262+
254263
const char *as_str = NULL;
255264
int is_data = jv_get_kind(jv_object_get(jv_copy(dep), jv_string("is_data"))) == JV_KIND_TRUE;
256265
int raw = 0;
@@ -420,14 +429,16 @@ int load_program(jq_state *jq, struct locfile* src, block *out_block) {
420429
return 1;
421430
}
422431

423-
char* home = getenv("HOME");
424-
if (home) { // silently ignore no $HOME
425-
/* Import ~/.jq as a library named "" found in $HOME */
426-
block import = gen_import_meta(gen_import("", NULL, 0),
427-
gen_const(JV_OBJECT(
428-
jv_string("optional"), jv_true(),
429-
jv_string("search"), jv_string(home))));
430-
program = BLOCK(import, program);
432+
if (!jq_is_sandbox(jq)) {
433+
char* home = getenv("HOME");
434+
if (home) { // silently ignore no $HOME
435+
/* Import ~/.jq as a library named "" found in $HOME */
436+
block import = gen_import_meta(gen_import("", NULL, 0),
437+
gen_const(JV_OBJECT(
438+
jv_string("optional"), jv_true(),
439+
jv_string("search"), jv_string(home))));
440+
program = BLOCK(import, program);
441+
}
431442
}
432443

433444
nerrors = process_dependencies(jq, jq_get_jq_origin(jq), jq_get_prog_origin(jq), &program, &lib_state);

src/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ static void usage(int code, int keep_it_short) {
106106
" --jsonargs consume remaining arguments as positional\n"
107107
" JSON values;\n"
108108
" -e, --exit-status set exit status code based on the output;\n"
109+
" --sandbox prevent dynamic access to other files/data;\n"
109110
#ifdef WIN32
110111
" -b, --binary open input/output streams in binary mode;\n"
111112
#endif
@@ -475,6 +476,10 @@ int main(int argc, char* argv[]) {
475476
parser_flags |= JV_PARSE_STREAMING | JV_PARSE_STREAM_ERRORS;
476477
continue;
477478
}
479+
if (isoption(argv[i], 0, "sandbox", &short_opts)) {
480+
jq_set_sandbox(jq);
481+
continue;
482+
}
478483
if (isoption(argv[i], 'e', "exit-status", &short_opts)) {
479484
options |= EXIT_STATUS;
480485
if (!short_opts) continue;

tests/shtest

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,21 @@ if ! $VALGRIND $Q $JQ -L tests/modules -ne 'import "test_bind_order" as check; c
381381
exit 1
382382
fi
383383

384+
if HOME="$mods/home1" $VALGRIND $Q $JQ --sandbox -nr fg; then
385+
echo "home module was loaded when it should have been prevented by sandbox flag" 1>&2
386+
exit 1
387+
fi
388+
389+
if HOME="$mods/home2" $VALGRIND $Q $JQ --sandbox -n 'include "g"; empty'; then
390+
echo "module was included when it should have been prevented by sandbox flag" 1>&2
391+
exit 1
392+
fi
393+
394+
if $VALGRIND $Q $JQ -L ./tests/modules --sandbox -n 'import "a" as a; empty'; then
395+
echo "module was imported when it should have been prevented by sandbox flag" 1>&2
396+
exit 1
397+
fi
398+
384399
## Halt
385400

386401
if ! $VALGRIND $Q $JQ -n halt; then

0 commit comments

Comments
 (0)