Last active
June 4, 2019 22:12
-
-
Save allanlw/c0d7166d2341d1aff6fae59c73443e2a to your computer and use it in GitHub Desktop.
Sandboxed Patch for jq
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Makefile.am b/Makefile.am | |
index 6344b4e..1a3a703 100644 | |
--- a/Makefile.am | |
+++ b/Makefile.am | |
@@ -5,13 +5,13 @@ LIBJQ_INCS = src/builtin.h src/bytecode.h src/compile.h \ | |
src/exec_stack.h src/jq_parser.h src/jv_alloc.h src/jv_dtoa.h \ | |
src/jv_unicode.h src/jv_utf8_tables.h src/lexer.l src/libm.h \ | |
src/linker.h src/locfile.h src/opcode_list.h src/parser.y \ | |
- src/util.h | |
+ src/util.h src/sandbox.h | |
LIBJQ_SRC = src/builtin.c src/bytecode.c src/compile.c src/execute.c \ | |
src/jq_test.c src/jv.c src/jv_alloc.c src/jv_aux.c \ | |
src/jv_dtoa.c src/jv_file.c src/jv_parse.c src/jv_print.c \ | |
src/jv_unicode.c src/linker.c src/locfile.c src/util.c \ | |
- ${LIBJQ_INCS} | |
+ src/sandbox.c ${LIBJQ_INCS} | |
### C build options | |
@@ -119,7 +119,7 @@ endif | |
### Tests (make check) | |
-TESTS = tests/optionaltest tests/mantest tests/jqtest tests/onigtest tests/shtest tests/utf8test tests/base64test | |
+TESTS = tests/optionaltest tests/mantest tests/jqtest tests/onigtest tests/shtest tests/utf8test tests/base64test tests/modulestest | |
TESTS_ENVIRONMENT = NO_VALGRIND=$(NO_VALGRIND) | |
diff --git a/configure.ac b/configure.ac | |
index 9186d00..172f4b3 100644 | |
--- a/configure.ac | |
+++ b/configure.ac | |
@@ -130,6 +130,9 @@ AC_FIND_FUNC([gmtime], [c], [#include <time.h>], [0]) | |
AC_FIND_FUNC([localtime_r], [c], [#include <time.h>], [0, 0]) | |
AC_FIND_FUNC([localtime], [c], [#include <time.h>], [0]) | |
AC_FIND_FUNC([gettimeofday], [c], [#include <time.h>], [0, 0]) | |
+AC_FIND_FUNC([prctl], [c], [#include <sys/prctl.h>], [0, 0]) | |
+AC_CHECK_DECL(PR_SET_SECCOMP, AC_DEFINE(HAVE_PR_SET_SECCOMP), , [#include <sys/prctl.h>]) | |
+AC_CHECK_DECL(SECCOMP_MODE_STRICT, AC_DEFINE(HAVE_SECCOMP_MODE_STRICT), , [#include <linux/seccomp.h>]) | |
AC_CHECK_MEMBER([struct tm.tm_gmtoff], [AC_DEFINE([HAVE_TM_TM_GMT_OFF],1,[Define to 1 if the system has the tm_gmt_off field in struct tm])], | |
[], [[#include <time.h>]]) | |
AC_CHECK_MEMBER([struct tm.__tm_gmtoff], [AC_DEFINE([HAVE_TM___TM_GMT_OFF],1,[Define to 1 if the system has the __tm_gmt_off field in struct tm])], | |
diff --git a/src/builtin.c b/src/builtin.c | |
index c6c8c2e..7ed4775 100644 | |
--- a/src/builtin.c | |
+++ b/src/builtin.c | |
@@ -1066,6 +1066,9 @@ extern char **environ; | |
static jv f_env(jq_state *jq, jv input) { | |
jv_free(input); | |
jv env = jv_object(); | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ return env; | |
+ } | |
const char *var, *val; | |
for (char **e = environ; *e != NULL; e++) { | |
var = e[0]; | |
@@ -1095,19 +1098,36 @@ static jv f_halt_error(jq_state *jq, jv input, jv a) { | |
static jv f_get_search_list(jq_state *jq, jv input) { | |
jv_free(input); | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ return jv_array(); | |
+ } | |
+ | |
return jq_get_lib_dirs(jq); | |
} | |
static jv f_get_prog_origin(jq_state *jq, jv input) { | |
jv_free(input); | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ return jv_string("<sandbox>"); | |
+ } | |
+ | |
return jq_get_prog_origin(jq); | |
} | |
static jv f_get_jq_origin(jq_state *jq, jv input) { | |
jv_free(input); | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ return jv_string("<sandbox>"); | |
+ } | |
+ | |
return jq_get_jq_origin(jq); | |
} | |
+static jv f_get_sandbox(jq_state *jq, jv input) { | |
+ jv_free(input); | |
+ return jq_get_sandbox(jq); | |
+} | |
+ | |
static jv f_string_split(jq_state *jq, jv a, jv b) { | |
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) { | |
return ret_error2(a, b, jv_string("split input and separator must be strings")); | |
@@ -1142,6 +1162,10 @@ static jv f_delpaths(jq_state *jq, jv a, jv b) { return jv_delpaths(a, b); } | |
static jv f_has(jq_state *jq, jv a, jv b) { return jv_has(a, b); } | |
static jv f_modulemeta(jq_state *jq, jv a) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ return jv_invalid_with_msg(jv_string("modulemeta not available in sandbox mode")); | |
+ } | |
if (jv_get_kind(a) != JV_KIND_STRING) { | |
return ret_error(a, jv_string("modulemeta input module name must be a string")); | |
} | |
@@ -1288,6 +1312,12 @@ static void set_tm_yday(struct tm *tm) { | |
#ifdef HAVE_STRPTIME | |
static jv f_strptime(jq_state *jq, jv a, jv b) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ jv_free(b); | |
+ return jv_invalid_with_msg(jv_string("strptime/1 not available in sandbox mode")); | |
+ } | |
+ | |
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) { | |
return ret_error2(a, b, jv_string("strptime/1 requires string inputs and arguments")); | |
} | |
@@ -1382,6 +1412,10 @@ static int jv2tm(jv a, struct tm *tm) { | |
#undef TO_TM_FIELD | |
static jv f_mktime(jq_state *jq, jv a) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ return jv_invalid_with_msg(jv_string("mktime not available in sandbox mode")); | |
+ } | |
if (jv_get_kind(a) != JV_KIND_ARRAY) | |
return ret_error(a, jv_string("mktime requires array inputs")); | |
if (jv_array_length(jv_copy(a)) < 6) | |
@@ -1399,6 +1433,10 @@ static jv f_mktime(jq_state *jq, jv a) { | |
#ifdef HAVE_GMTIME_R | |
static jv f_gmtime(jq_state *jq, jv a) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ return jv_invalid_with_msg(jv_string("gmtime not available in sandbox mode")); | |
+ } | |
if (jv_get_kind(a) != JV_KIND_NUMBER) | |
return ret_error(a, jv_string("gmtime() requires numeric inputs")); | |
struct tm tm, *tmp; | |
@@ -1414,6 +1452,10 @@ static jv f_gmtime(jq_state *jq, jv a) { | |
} | |
#elif defined HAVE_GMTIME | |
static jv f_gmtime(jq_state *jq, jv a) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ return jv_invalid_with_msg(jv_string("gmtime not available in sandbox mode")); | |
+ } | |
if (jv_get_kind(a) != JV_KIND_NUMBER) | |
return ret_error(a, jv_string("gmtime requires numeric inputs")); | |
struct tm tm, *tmp; | |
@@ -1436,6 +1478,11 @@ static jv f_gmtime(jq_state *jq, jv a) { | |
#ifdef HAVE_LOCALTIME_R | |
static jv f_localtime(jq_state *jq, jv a) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ return jv_invalid_with_msg(jv_string("localtime not available in sandbox mode")); | |
+ } | |
+ | |
if (jv_get_kind(a) != JV_KIND_NUMBER) | |
return ret_error(a, jv_string("localtime() requires numeric inputs")); | |
struct tm tm, *tmp; | |
@@ -1451,6 +1498,11 @@ static jv f_localtime(jq_state *jq, jv a) { | |
} | |
#elif defined HAVE_GMTIME | |
static jv f_localtime(jq_state *jq, jv a) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ return jv_invalid_with_msg(jv_string("localtime not available in sandbox mode")); | |
+ } | |
+ | |
if (jv_get_kind(a) != JV_KIND_NUMBER) | |
return ret_error(a, jv_string("localtime requires numeric inputs")); | |
struct tm tm, *tmp; | |
@@ -1473,6 +1525,12 @@ static jv f_localtime(jq_state *jq, jv a) { | |
#ifdef HAVE_STRFTIME | |
static jv f_strftime(jq_state *jq, jv a, jv b) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ jv_free(b); | |
+ return jv_invalid_with_msg(jv_string("strftime/1 not available in sandbox mode")); | |
+ } | |
+ | |
if (jv_get_kind(a) == JV_KIND_NUMBER) { | |
a = f_gmtime(jq, a); | |
} else if (jv_get_kind(a) != JV_KIND_ARRAY) { | |
@@ -1504,6 +1562,12 @@ static jv f_strftime(jq_state *jq, jv a, jv b) { | |
#ifdef HAVE_STRFTIME | |
static jv f_strflocaltime(jq_state *jq, jv a, jv b) { | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ jv_free(a); | |
+ jv_free(b); | |
+ return jv_invalid_with_msg(jv_string("strflocaltime/1 not available in sandbox mode")); | |
+ } | |
+ | |
if (jv_get_kind(a) == JV_KIND_NUMBER) { | |
a = f_localtime(jq, a); | |
} else if (jv_get_kind(a) != JV_KIND_ARRAY) { | |
@@ -1535,6 +1599,9 @@ static jv f_strflocaltime(jq_state *jq, jv a, jv b) { | |
#ifdef HAVE_GETTIMEOFDAY | |
static jv f_now(jq_state *jq, jv a) { | |
jv_free(a); | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ return jv_invalid_with_msg(jv_string("now not available in sandbox mode")); | |
+ } | |
struct timeval tv; | |
if (gettimeofday(&tv, NULL) == -1) | |
return jv_number(time(NULL)); | |
@@ -1543,6 +1610,9 @@ static jv f_now(jq_state *jq, jv a) { | |
#else | |
static jv f_now(jq_state *jq, jv a) { | |
jv_free(a); | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ return jv_invalid_with_msg(jv_string("now not available in sandbox mode")); | |
+ } | |
return jv_number(time(NULL)); | |
} | |
#endif | |
@@ -1550,6 +1620,10 @@ static jv f_now(jq_state *jq, jv a) { | |
static jv f_current_filename(jq_state *jq, jv a) { | |
jv_free(a); | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ return jv_string("<sandbox>"); | |
+ } | |
+ | |
jv r = jq_util_input_get_current_filename(jq); | |
if (jv_is_valid(r)) | |
return r; | |
@@ -1638,6 +1712,7 @@ static const struct cfunction function_list[] = { | |
{(cfunction_ptr)f_get_search_list, "get_search_list", 1}, | |
{(cfunction_ptr)f_get_prog_origin, "get_prog_origin", 1}, | |
{(cfunction_ptr)f_get_jq_origin, "get_jq_origin", 1}, | |
+ {(cfunction_ptr)f_get_sandbox, "get_sandbox", 1}, | |
{(cfunction_ptr)f_match, "_match_impl", 4}, | |
{(cfunction_ptr)f_modulemeta, "modulemeta", 1}, | |
{(cfunction_ptr)f_input, "_input", 1}, | |
@@ -1759,6 +1834,12 @@ static int builtins_bind_one(jq_state *jq, block* bb, const char* code) { | |
static int slurp_lib(jq_state *jq, block* bb) { | |
int nerrors = 0; | |
char* home = getenv("HOME"); | |
+ | |
+ // No implicitly sourced HOME library in sandbox mode | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true())) { | |
+ return 0; | |
+ } | |
+ | |
if (home) { // silently ignore no $HOME | |
jv filename = jv_string_append_str(jv_string(home), "/.jq"); | |
jv data = jv_load_file(jv_string_value(filename), 1); | |
diff --git a/src/compile.c b/src/compile.c | |
index 33a8e72..51c3c2c 100644 | |
--- a/src/compile.c | |
+++ b/src/compile.c | |
@@ -1112,39 +1112,13 @@ static int count_cfunctions(block b) { | |
return n; | |
} | |
-#ifndef WIN32 | |
-extern char **environ; | |
-#endif | |
- | |
-static jv | |
-make_env(jv env) | |
-{ | |
- if (jv_is_valid(env)) | |
- return jv_copy(env); | |
- jv r = jv_object(); | |
- if (environ == NULL) | |
- return r; | |
- for (size_t i = 0; environ[i] != NULL; i++) { | |
- const char *eq; | |
- | |
- if ((eq = strchr(environ[i], '=')) == NULL) | |
- r = jv_object_delete(r, jv_string(environ[i])); | |
- else | |
- r = jv_object_set(r, jv_string_sized(environ[i], eq - environ[i]), jv_string(eq + 1)); | |
- } | |
- return jv_copy(r); | |
-} | |
- | |
// Expands call instructions into a calling sequence | |
-static int expand_call_arglist(block* b, jv args, jv *env) { | |
+static int expand_call_arglist(block* b, jv args) { | |
int errors = 0; | |
block ret = gen_noop(); | |
for (inst* curr; (curr = block_take(b));) { | |
if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) { | |
- if (!curr->bound_by && curr->op == LOADV && strcmp(curr->symbol, "ENV") == 0) { | |
- curr->op = LOADK; | |
- *env = curr->imm.constant = make_env(*env); | |
- } else if (!curr->bound_by && curr->op == LOADV && jv_object_has(jv_copy(args), jv_string(curr->symbol))) { | |
+ if (!curr->bound_by && curr->op == LOADV && jv_object_has(jv_copy(args), jv_string(curr->symbol))) { | |
curr->op = LOADK; | |
curr->imm.constant = jv_object_get(jv_copy(args), jv_string(curr->symbol)); | |
} else if (!curr->bound_by) { | |
@@ -1204,7 +1178,7 @@ static int expand_call_arglist(block* b, jv args, jv *env) { | |
i->subfn = gen_noop(); | |
inst_free(i); | |
// arguments should be pushed in reverse order, prepend them to prelude | |
- errors += expand_call_arglist(&body, args, env); | |
+ errors += expand_call_arglist(&body, args); | |
prelude = BLOCK(gen_subexp(body), prelude); | |
actual_args++; | |
} | |
@@ -1226,12 +1200,12 @@ static int expand_call_arglist(block* b, jv args, jv *env) { | |
return errors; | |
} | |
-static int compile(struct bytecode* bc, block b, struct locfile* lf, jv args, jv *env) { | |
+static int compile(struct bytecode* bc, block b, struct locfile* lf, jv args) { | |
int errors = 0; | |
int pos = 0; | |
int var_frame_idx = 0; | |
bc->nsubfunctions = 0; | |
- errors += expand_call_arglist(&b, args, env); | |
+ errors += expand_call_arglist(&b, args); | |
b = BLOCK(b, gen_op_simple(RET)); | |
jv localnames = jv_array(); | |
for (inst* curr = b.first; curr; curr = curr->next) { | |
@@ -1294,7 +1268,7 @@ static int compile(struct bytecode* bc, block b, struct locfile* lf, jv args, jv | |
params = jv_array_append(params, jv_string(param->symbol)); | |
} | |
subfn->debuginfo = jv_object_set(subfn->debuginfo, jv_string("params"), params); | |
- errors += compile(subfn, curr->subfn, lf, args, env); | |
+ errors += compile(subfn, curr->subfn, lf, args); | |
curr->subfn = gen_noop(); | |
} | |
} | |
@@ -1369,10 +1343,8 @@ int block_compile(block b, struct bytecode** out, struct locfile* lf, jv args) { | |
bc->globals->cfunctions = jv_mem_calloc(sizeof(struct cfunction), ncfunc); | |
bc->globals->cfunc_names = jv_array(); | |
bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null()); | |
- jv env = jv_invalid(); | |
- int nerrors = compile(bc, b, lf, args, &env); | |
+ int nerrors = compile(bc, b, lf, args); | |
jv_free(args); | |
- jv_free(env); | |
assert(bc->globals->ncfunctions == ncfunc); | |
if (nerrors > 0) { | |
bytecode_free(bc); | |
diff --git a/src/execute.c b/src/execute.c | |
index d567216..e9e9f52 100644 | |
--- a/src/execute.c | |
+++ b/src/execute.c | |
@@ -1186,6 +1186,10 @@ jv jq_get_lib_dirs(jq_state *jq) { | |
return jq_get_attr(jq, jv_string("JQ_LIBRARY_PATH")); | |
} | |
+jv jq_get_sandbox(jq_state *jq) { | |
+ return jq_get_attr(jq, jv_string("JQ_SANDBOX")); | |
+} | |
+ | |
void jq_set_attrs(jq_state *jq, jv attrs) { | |
assert(jv_get_kind(attrs) == JV_KIND_OBJECT); | |
jv_free(jq->attrs); | |
diff --git a/src/jq.h b/src/jq.h | |
index 5269de3..6ace84b 100644 | |
--- a/src/jq.h | |
+++ b/src/jq.h | |
@@ -43,6 +43,7 @@ jv jq_get_attrs(jq_state *); | |
jv jq_get_jq_origin(jq_state *); | |
jv jq_get_prog_origin(jq_state *); | |
jv jq_get_lib_dirs(jq_state *); | |
+jv jq_get_sandbox(jq_state *); | |
void jq_set_attr(jq_state *, jv, jv); | |
jv jq_get_attr(jq_state *, jv); | |
diff --git a/src/jq_test.c b/src/jq_test.c | |
index 7a396b9..43210f2 100644 | |
--- a/src/jq_test.c | |
+++ b/src/jq_test.c | |
@@ -5,12 +5,15 @@ | |
#include "jv.h" | |
#include "jq.h" | |
+#include "sandbox.h" | |
+ | |
static void jv_test(); | |
-static void run_jq_tests(jv, int, FILE *); | |
+static void run_jq_tests(jv, int, int, FILE *); | |
int jq_testsuite(jv libdirs, int verbose, int argc, char* argv[]) { | |
FILE *testdata = stdin; | |
+ int sandbox = 0; | |
jv_test(); | |
if (argc > 0) { | |
testdata = fopen(argv[0], "r"); | |
@@ -18,8 +21,12 @@ int jq_testsuite(jv libdirs, int verbose, int argc, char* argv[]) { | |
perror("fopen"); | |
exit(1); | |
} | |
+ if (argc > 1 && strcmp(argv[1], "sandbox") == 0) { | |
+ enter_sandbox(); | |
+ sandbox = 1; | |
+ } | |
} | |
- run_jq_tests(libdirs, verbose, testdata); | |
+ run_jq_tests(libdirs, verbose, sandbox, testdata); | |
return 0; | |
} | |
@@ -53,7 +60,7 @@ static void test_err_cb(void *data, jv e) { | |
jv_free(e); | |
} | |
-static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata) { | |
+static void run_jq_tests(jv lib_dirs, int verbose, int sandbox, FILE *testdata) { | |
char prog[4096]; | |
char buf[4096]; | |
struct err_data err_msg; | |
@@ -68,6 +75,7 @@ static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata) { | |
if (jv_get_kind(lib_dirs) == JV_KIND_NULL) | |
lib_dirs = jv_array(); | |
jq_set_attr(jq, jv_string("JQ_LIBRARY_PATH"), lib_dirs); | |
+ jq_set_attr(jq, jv_string("JQ_SANDBOX"), jv_bool(sandbox)); | |
while (1) { | |
if (!fgets(prog, sizeof(prog), testdata)) break; | |
@@ -132,7 +140,7 @@ static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata) { | |
invalid++; | |
continue; | |
} | |
- jq_start(jq, input, verbose ? JQ_DEBUG_TRACE : 0); | |
+ jq_start(jq, input, (verbose ? JQ_DEBUG_TRACE : 0)); | |
while (fgets(buf, sizeof(buf), testdata)) { | |
lineno++; | |
diff --git a/src/linker.c b/src/linker.c | |
index 1b9d62c..1828bb5 100644 | |
--- a/src/linker.c | |
+++ b/src/linker.c | |
@@ -235,6 +235,14 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block | |
int nerrors = 0; | |
const char *as_str = NULL; | |
+ if (jv_equal(jq_get_sandbox(jq), jv_true()) && jv_array_length(jv_copy(deps)) > 0) { | |
+ jq_report_error(jq, jv_string_fmt("jq: library depdencies cannot be used with sandbox mode")); | |
+ jv_free(deps); | |
+ jv_free(jq_origin); | |
+ jv_free(lib_origin); | |
+ return 1; | |
+ } | |
+ | |
jv_array_foreach(deps, i, dep) { | |
int is_data = jv_get_kind(jv_object_get(jv_copy(dep), jv_string("is_data"))) == JV_KIND_TRUE; | |
int raw = 0; | |
diff --git a/src/main.c b/src/main.c | |
index be436dd..9ff2094 100644 | |
--- a/src/main.c | |
+++ b/src/main.c | |
@@ -30,6 +30,7 @@ | |
#include "jv.h" | |
#include "jq.h" | |
#include "jv_alloc.h" | |
+#include "sandbox.h" | |
#include "util.h" | |
#include "src/version.h" | |
@@ -141,8 +142,9 @@ enum { | |
EXIT_STATUS_EXACT = 8192, | |
SEQ = 16384, | |
RUN_TESTS = 32768, | |
+ SANDBOX = 65536, | |
/* debugging only */ | |
- DUMP_DISASM = 65536, | |
+ DUMP_DISASM = 131072, | |
}; | |
static int options = 0; | |
@@ -226,6 +228,25 @@ static int process(jq_state *jq, jv value, int flags, int dumpopts) { | |
return ret; | |
} | |
+#ifndef WIN32 | |
+extern char **environ; | |
+#endif | |
+ | |
+static jv make_env(void) { | |
+ jv r = jv_object(); | |
+ if (environ == NULL) | |
+ return r; | |
+ for (size_t i = 0; environ[i] != NULL; i++) { | |
+ const char *eq; | |
+ | |
+ if ((eq = strchr(environ[i], '=')) == NULL) | |
+ r = jv_object_delete(r, jv_string(environ[i])); | |
+ else | |
+ r = jv_object_set(r, jv_string_sized(environ[i], eq - environ[i]), jv_string(eq + 1)); | |
+ } | |
+ return jv_copy(r); | |
+} | |
+ | |
static void debug_cb(void *data, jv input) { | |
int dumpopts = *(int *)data; | |
jv_dumpf(JV_ARRAY(jv_string("DEBUG:"), input), stderr, dumpopts & ~(JV_PRINT_PRETTY)); | |
@@ -488,6 +509,10 @@ int main(int argc, char* argv[]) { | |
jq_flags |= JQ_DEBUG_TRACE; | |
continue; | |
} | |
+ if (isoption(argv[i], 0, "sandbox", &short_opts)) { | |
+ options |= SANDBOX; | |
+ continue; | |
+ } | |
if (isoption(argv[i], 'h', "help", &short_opts)) { | |
usage(0, 0); | |
if (!short_opts) continue; | |
@@ -533,14 +558,6 @@ int main(int argc, char* argv[]) { | |
if (getenv("JQ_COLORS") != NULL && !jq_set_colors(getenv("JQ_COLORS"))) | |
fprintf(stderr, "Failed to set $JQ_COLORS\n"); | |
- if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) { | |
- // Default search path list | |
- lib_search_paths = JV_ARRAY(jv_string("~/.jq"), | |
- jv_string("$ORIGIN/../lib/jq"), | |
- jv_string("$ORIGIN/lib")); | |
- } | |
- jq_set_attr(jq, jv_string("JQ_LIBRARY_PATH"), lib_search_paths); | |
- | |
char *origin = strdup(argv[0]); | |
if (origin == NULL) { | |
fprintf(stderr, "Error: out of memory\n"); | |
@@ -549,11 +566,6 @@ int main(int argc, char* argv[]) { | |
jq_set_attr(jq, jv_string("JQ_ORIGIN"), jv_string(dirname(origin))); | |
free(origin); | |
- if (strchr(JQ_VERSION, '-') == NULL) | |
- jq_set_attr(jq, jv_string("VERSION_DIR"), jv_string(JQ_VERSION)); | |
- else | |
- jq_set_attr(jq, jv_string("VERSION_DIR"), jv_string_fmt("%.*s-master", (int)(strchr(JQ_VERSION, '-') - JQ_VERSION), JQ_VERSION)); | |
- | |
#ifdef USE_ISATTY | |
if (!program && (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO))) | |
program = "."; | |
@@ -567,6 +579,8 @@ int main(int argc, char* argv[]) { | |
perror("malloc"); | |
exit(2); | |
} | |
+ jq_set_attr(jq, jv_string("PROGRAM_ORIGIN"), jq_realpath(jv_string(dirname(program_origin)))); | |
+ free(program_origin); | |
jv data = jv_load_file(program, 1); | |
if (!jv_is_valid(data)) { | |
@@ -576,20 +590,41 @@ int main(int argc, char* argv[]) { | |
ret = 2; | |
goto out; | |
} | |
- jq_set_attr(jq, jv_string("PROGRAM_ORIGIN"), jq_realpath(jv_string(dirname(program_origin)))); | |
- ARGS = JV_OBJECT(jv_string("positional"), ARGS, | |
- jv_string("named"), jv_copy(program_arguments)); | |
- program_arguments = jv_object_set(program_arguments, jv_string("ARGS"), jv_copy(ARGS)); | |
- compiled = jq_compile_args(jq, skip_shebang(jv_string_value(data)), jv_copy(program_arguments)); | |
- free(program_origin); | |
- jv_free(data); | |
+ program = skip_shebang(jv_string_value(data)); | |
} else { | |
jq_set_attr(jq, jv_string("PROGRAM_ORIGIN"), jq_realpath(jv_string("."))); // XXX is this good? | |
- ARGS = JV_OBJECT(jv_string("positional"), ARGS, | |
- jv_string("named"), jv_copy(program_arguments)); | |
- program_arguments = jv_object_set(program_arguments, jv_string("ARGS"), jv_copy(ARGS)); | |
- compiled = jq_compile_args(jq, program, jv_copy(program_arguments)); | |
} | |
+ | |
+ ARGS = JV_OBJECT(jv_string("positional"), ARGS, | |
+ jv_string("named"), jv_copy(program_arguments)); | |
+ program_arguments = jv_object_set(program_arguments, jv_string("ARGS"), jv_copy(ARGS)); | |
+ if (!(options & SANDBOX)) { | |
+ program_arguments = jv_object_set(program_arguments, jv_string("ENV"), make_env()); | |
+ } | |
+ | |
+ jq_set_attr(jq, jv_string("JQ_SANDBOX"), jv_bool((options & SANDBOX))); | |
+ if (options & SANDBOX) { | |
+ // no search path available in sandbox mode | |
+ lib_search_paths = jv_array(); | |
+ | |
+ if (enter_sandbox()) { | |
+ die(); | |
+ } | |
+ } else if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) { | |
+ // Default search path list | |
+ lib_search_paths = JV_ARRAY(jv_string("~/.jq"), | |
+ jv_string("$ORIGIN/../lib/jq"), | |
+ jv_string("$ORIGIN/lib")); | |
+ } | |
+ jq_set_attr(jq, jv_string("JQ_LIBRARY_PATH"), lib_search_paths); | |
+ | |
+ if (strchr(JQ_VERSION, '-') == NULL) | |
+ jq_set_attr(jq, jv_string("VERSION_DIR"), jv_string(JQ_VERSION)); | |
+ else | |
+ jq_set_attr(jq, jv_string("VERSION_DIR"), jv_string_fmt("%.*s-master", (int)(strchr(JQ_VERSION, '-') - JQ_VERSION), JQ_VERSION)); | |
+ | |
+ compiled = jq_compile_args(jq, program, jv_copy(program_arguments)); | |
+ | |
if (!compiled){ | |
ret = 3; | |
goto out; | |
diff --git a/src/sandbox.c b/src/sandbox.c | |
new file mode 100644 | |
index 0000000..7bf1816 | |
--- /dev/null | |
+++ b/src/sandbox.c | |
@@ -0,0 +1,55 @@ | |
+#include "sandbox.h" | |
+#include <errno.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+ | |
+#ifdef HAVE_PRCTL | |
+#include <sys/prctl.h> | |
+#include <linux/unistd.h> | |
+#endif | |
+ | |
+#ifdef HAVE_SECCOMP_MODE_STRICT | |
+#include <linux/seccomp.h> | |
+#include <linux/filter.h> | |
+#endif | |
+ | |
+int enter_sandbox(void) { | |
+#if !defined(HAVE_PR_SET_SECCOMP) || !defined(HAVE_SECCOMP_MODE_STRICT) || !defined(HAVE_PRCTL) | |
+ fprintf(stderr, "Sandbox mode not supported\n"); | |
+ return 1; | |
+#else | |
+ // This is a very simple seccomp (mode 2) filter implemented from https://outflux.net/teach-seccomp/ | |
+ struct sock_filter filter[] = { | |
+ // Load syscall number | |
+ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 0), | |
+ | |
+#define ALLOW_SYSCALL(name) \ | |
+ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \ | |
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) | |
+ | |
+ ALLOW_SYSCALL(read), | |
+ ALLOW_SYSCALL(write), | |
+ ALLOW_SYSCALL(brk), | |
+ ALLOW_SYSCALL(fstat), | |
+ ALLOW_SYSCALL(mmap), | |
+ ALLOW_SYSCALL(munmap), | |
+ ALLOW_SYSCALL(close), | |
+ ALLOW_SYSCALL(exit), | |
+ ALLOW_SYSCALL(exit_group), | |
+ | |
+ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRAP), | |
+ }; | |
+ struct sock_fprog program = { | |
+ .len = sizeof(filter)/sizeof(filter[0]), | |
+ .filter = filter, | |
+ }; | |
+ | |
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) || | |
+ prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program, 0, 0)) { | |
+ perror("error enabling seccomp"); | |
+ return 1; | |
+ } | |
+ return 0; | |
+#endif | |
+} | |
diff --git a/src/sandbox.h b/src/sandbox.h | |
new file mode 100644 | |
index 0000000..222ae07 | |
--- /dev/null | |
+++ b/src/sandbox.h | |
@@ -0,0 +1,6 @@ | |
+#ifndef SANDBOX_H | |
+#define SANDBOX_H | |
+ | |
+int enter_sandbox(void); | |
+ | |
+#endif | |
diff --git a/tests/base64test b/tests/base64test | |
index 85fe64b..5fc53e9 100755 | |
--- a/tests/base64test | |
+++ b/tests/base64test | |
@@ -2,4 +2,4 @@ | |
. "${0%/*}/setup" "$@" | |
-$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/base64.test | |
+$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/base64.test sandbox | |
diff --git a/tests/jq.test b/tests/jq.test | |
index 8771ba6..ae96369 100644 | |
--- a/tests/jq.test | |
+++ b/tests/jq.test | |
@@ -1242,69 +1242,6 @@ bsearch(4) | |
[1,2,3] | |
-4 | |
-# strptime tests are in optional.test | |
- | |
-strftime("%Y-%m-%dT%H:%M:%SZ") | |
-[2015,2,5,23,51,47,4,63] | |
-"2015-03-05T23:51:47Z" | |
- | |
-strftime("%A, %B %d, %Y") | |
-1435677542.822351 | |
-"Tuesday, June 30, 2015" | |
- | |
-gmtime | |
-1425599507 | |
-[2015,2,5,23,51,47,4,63] | |
- | |
-# module system | |
-import "a" as foo; import "b" as bar; def fooa: foo::a; [fooa, bar::a, bar::b, foo::a] | |
-null | |
-["a","b","c","a"] | |
- | |
-import "c" as foo; [foo::a, foo::c] | |
-null | |
-[0,"acmehbah"] | |
- | |
-include "c"; [a, c] | |
-null | |
-[0,"acmehbah"] | |
- | |
-%%FAIL | |
-module (.+1); 0 | |
-jq: error: Module metadata must be constant at <top-level>, line 1: | |
- | |
-%%FAIL | |
-include "a" (.+1); 0 | |
-jq: error: Module metadata must be constant at <top-level>, line 1: | |
- | |
-%%FAIL | |
-include "a" []; 0 | |
-jq: error: Module metadata must be an object at <top-level>, line 1: | |
- | |
-%%FAIL | |
-include "\ "; 0 | |
-jq: error: Invalid escape at line 1, column 4 (while parsing '"\ "') at <top-level>, line 1: | |
- | |
-%%FAIL | |
-include "\(a)"; 0 | |
-jq: error: Import path must be constant at <top-level>, line 1: | |
- | |
-modulemeta | |
-"c" | |
-{"whatever":null,"deps":[{"as":"foo","is_data":false,"relpath":"a"},{"search":"./","as":"d","is_data":false,"relpath":"d"},{"search":"./","as":"d2","is_data":false,"relpath":"d"},{"search":"./../lib/jq","as":"e","is_data":false,"relpath":"e"},{"search":"./../lib/jq","as":"f","is_data":false,"relpath":"f"},{"as":"d","is_data":true,"relpath":"data"}]} | |
- | |
-%%FAIL IGNORE MSG | |
-import "syntaxerror" as e; . | |
-jq: error: syntax error, unexpected ';', expecting $end (Unix shell quoting issues?) at /home/nico/ws/jq/tests/modules/syntaxerror/syntaxerror.jq, line 1: | |
- | |
-%%FAIL IGNORE MSG | |
-%::wat | |
-jq: error: syntax error, unexpected '%', expecting $end (Unix shell quoting issues?) at <top-level>, line 1: | |
- | |
-import "test_bind_order" as check; check::check | |
-null | |
-true | |
- | |
try -. catch . | |
"very-long-string" | |
"string (\"very-long-...) cannot be negated" | |
diff --git a/tests/jqtest b/tests/jqtest | |
index 2420e03..db1f4f8 100755 | |
--- a/tests/jqtest | |
+++ b/tests/jqtest | |
@@ -2,4 +2,4 @@ | |
. "${0%/*}/setup" "$@" | |
-$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/jq.test | |
+$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/jq.test sandbox | |
diff --git a/tests/modules.test b/tests/modules.test | |
new file mode 100644 | |
index 0000000..4a77810 | |
--- /dev/null | |
+++ b/tests/modules.test | |
@@ -0,0 +1,52 @@ | |
+# Tests are groups of three lines: program, input, expected output | |
+# Blank lines and lines starting with # are ignored | |
+ | |
+# module system | |
+import "a" as foo; import "b" as bar; def fooa: foo::a; [fooa, bar::a, bar::b, foo::a] | |
+null | |
+["a","b","c","a"] | |
+ | |
+import "c" as foo; [foo::a, foo::c] | |
+null | |
+[0,"acmehbah"] | |
+ | |
+include "c"; [a, c] | |
+null | |
+[0,"acmehbah"] | |
+ | |
+%%FAIL | |
+module (.+1); 0 | |
+jq: error: Module metadata must be constant at <top-level>, line 1: | |
+ | |
+%%FAIL | |
+include "a" (.+1); 0 | |
+jq: error: Module metadata must be constant at <top-level>, line 1: | |
+ | |
+%%FAIL | |
+include "a" []; 0 | |
+jq: error: Module metadata must be an object at <top-level>, line 1: | |
+ | |
+%%FAIL | |
+include "\ "; 0 | |
+jq: error: Invalid escape at line 1, column 4 (while parsing '"\ "') at <top-level>, line 1: | |
+ | |
+%%FAIL | |
+include "\(a)"; 0 | |
+jq: error: Import path must be constant at <top-level>, line 1: | |
+ | |
+modulemeta | |
+"c" | |
+{"whatever":null,"deps":[{"as":"foo","is_data":false,"relpath":"a"},{"search":"./","as":"d","is_data":false,"relpath":"d"},{"search":"./","as":"d2","is_data":false,"relpath":"d"},{"search":"./../lib/jq","as":"e","is_data":false,"relpath":"e"},{"search":"./../lib/jq","as":"f","is_data":false,"relpath":"f"},{"as":"d","is_data":true,"relpath":"data"}]} | |
+ | |
+%%FAIL IGNORE MSG | |
+import "syntaxerror" as e; . | |
+jq: error: syntax error, unexpected ';', expecting $end (Unix shell quoting issues?) at /home/nico/ws/jq/tests/modules/syntaxerror/syntaxerror.jq, line 1: | |
+ | |
+%%FAIL IGNORE MSG | |
+%::wat | |
+jq: error: syntax error, unexpected '%', expecting $end (Unix shell quoting issues?) at <top-level>, line 1: | |
+ | |
+import "test_bind_order" as check; check::check | |
+null | |
+true | |
+ | |
diff --git a/tests/modulestest b/tests/modulestest | |
new file mode 100755 | |
index 0000000..69f5bb4 | |
--- /dev/null | |
+++ b/tests/modulestest | |
@@ -0,0 +1,5 @@ | |
+#!/bin/sh | |
+ | |
+. "${0%/*}/setup" "$@" | |
+ | |
+$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/modules.test | |
diff --git a/tests/onigtest b/tests/onigtest | |
index f452193..f2b3b71 100755 | |
--- a/tests/onigtest | |
+++ b/tests/onigtest | |
@@ -2,4 +2,4 @@ | |
. "${0%/*}/setup" "$@" | |
-$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/onig.test | |
+$VALGRIND $Q $JQ -L "$mods" --run-tests $JQTESTDIR/onig.test sandbox | |
diff --git a/tests/optional.test b/tests/optional.test | |
index 85bc9e9..abf630c 100644 | |
--- a/tests/optional.test | |
+++ b/tests/optional.test | |
@@ -1,5 +1,18 @@ | |
# See tests/jq.test and the jq manual for more information. | |
+# strftime can require timezone information not available in the sandbox | |
+strftime("%Y-%m-%dT%H:%M:%SZ") | |
+[2015,2,5,23,51,47,4,63] | |
+"2015-03-05T23:51:47Z" | |
+ | |
+strftime("%A, %B %d, %Y") | |
+1435677542.822351 | |
+"Tuesday, June 30, 2015" | |
+ | |
+gmtime | |
+1425599507 | |
+[2015,2,5,23,51,47,4,63] | |
+ | |
# strptime() is not available on mingw/WIN32 | |
[strptime("%Y-%m-%dT%H:%M:%SZ")|(.,mktime)] | |
"2015-03-05T23:51:47Z" | |
diff --git a/tests/utf8test b/tests/utf8test | |
index 570731b..50f04ce 100755 | |
--- a/tests/utf8test | |
+++ b/tests/utf8test | |
@@ -2,7 +2,7 @@ | |
. "${0%/*}/setup" "$@" | |
-if [ "`$VALGRIND $Q $JQ -nf $JQTESTDIR/utf8-truncate.jq`" != "true" ]; then | |
+if [ "`$VALGRIND $Q $JQ --sandbox -nf $JQTESTDIR/utf8-truncate.jq`" != "true" ]; then | |
echo "UTF-8 byte sequences that span the jv_load_file read buffer are mangled" | |
exit 1 | |
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Copyright 2019 Akamai Technologies, Inc. | |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment