Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --sandbox flag to prevent dynamic loading of other files/data. #3092

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
14 changes: 14 additions & 0 deletions docs/content/manual/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,17 @@ 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.

This flag also hides all environment variables from the enviroment
where jq was run by setting `$ENV` and `env` to be an empty object.
If you need to pass named arguments to a sandboxed jq filter, use the
`--arg` and/or `--argjson` options to pass them explicitly.

* `--binary` / `-b`:

Windows users using WSL, MSYS2, or Cygwin, should use this option
Expand Down Expand Up @@ -2013,6 +2024,9 @@ sections:

`env` outputs an object representing jq's current environment.

`$ENV` and `env` will be an empty object if jq was run with the
`--sandbox` flag.

At the moment there is no builtin for setting environment
variables.

Expand Down
14 changes: 13 additions & 1 deletion jq.1.prebuilt

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

5 changes: 5 additions & 0 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,11 @@ extern char **environ;
static jv f_env(jq_state *jq, jv input) {
jv_free(input);
jv env = jv_object();

// A sandboxed filter doesn't have access to environment variables,
// so in such a case we return the empty object without using environ.
if (jq_is_sandbox(jq)) return env;

const char *var, *val;
for (char **e = environ; *e != NULL; e++) {
var = e[0];
Expand Down
10 changes: 8 additions & 2 deletions src/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1367,7 +1367,7 @@ static int compile(struct bytecode* bc, block b, struct locfile* lf, jv args, jv
return errors;
}

int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) {
int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args, int is_sandbox) {
struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode));
bc->parent = 0;
bc->nclosures = 0;
Expand All @@ -1377,7 +1377,13 @@ int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) {
bc->globals->cfunctions = jv_mem_calloc(ncfunc, sizeof(struct cfunction));
bc->globals->cfunc_names = jv_array();
bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null());
jv env = jv_invalid();

// When sandboxed, we don't want to expose environment vars to the program,
// so we create an empty object which is already valid. This prevents a
// later step from creating a populated `$ENV` object, because that step
// only does so if the current value for `env` is invalid.
jv env = is_sandbox ? jv_object() : jv_invalid();

int nerrors = compile(bc, b, lf, args, &env);
jv_free(args);
jv_free(env);
Expand Down
2 changes: 1 addition & 1 deletion src/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ block block_drop_unreferenced(block body);
jv block_take_imports(block* body);
jv block_list_funcs(block body, int omit_underscores);

int block_compile(block, struct bytecode**, struct locfile*, jv);
int block_compile(block, struct bytecode**, struct locfile*, jv, int is_sandbox);

void block_free(block);

Expand Down
12 changes: 11 additions & 1 deletion 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 @@ -1244,7 +1246,7 @@ int jq_compile_args(jq_state *jq, const char* str, jv args) {
if (nerrors == 0) {
nerrors = builtins_bind(jq, &program);
if (nerrors == 0) {
nerrors = block_compile(program, &jq->bc, locations, args2obj(args));
nerrors = block_compile(program, &jq->bc, locations, args2obj(args), jq_is_sandbox(jq));
}
} else
jv_free(args);
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
37 changes: 37 additions & 0 deletions tests/shtest
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,43 @@ 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

## Test environment variable access

if [ "$(FOO=foo $VALGRIND $Q $JQ -nr '$ENV.FOO')" != foo ]; then
echo "couldn't read an environment variable via \$ENV" 1>&2
exit 1
fi

if [ "$(FOO=foo $VALGRIND $Q $JQ --sandbox -nr '$ENV.FOO')" != null ]; then
echo "\$ENV should have been empty due to the sandbox flag" 1>&2
exit 1
fi

if [ "$(FOO=foo $VALGRIND $Q $JQ -nr 'env.FOO')" != foo ]; then
echo "couldn't read an environment variable via env" 1>&2
exit 1
fi

if [ "$(FOO=foo $VALGRIND $Q $JQ --sandbox -nr 'env.FOO')" != null ]; then
echo "env should have been empty due to the sandbox flag" 1>&2
exit 1
fi

## Halt

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