diff options
Diffstat (limited to 'tests/test_runner.c')
| -rw-r--r-- | tests/test_runner.c | 135 |
1 files changed, 134 insertions, 1 deletions
diff --git a/tests/test_runner.c b/tests/test_runner.c index 172368b..a3e2b5a 100644 --- a/tests/test_runner.c +++ b/tests/test_runner.c @@ -91,11 +91,144 @@ test_injection_guard(void) { g_free(d); } +/* Observable injection test: a hostile filename containing shell + * metacharacters that, if executed, would create a sentinel file. The + * single-quoted %f must prevent the ';' from being interpreted. */ +static void +test_hostile_filename(void) { + GError *e = NULL; + char *d = g_dir_make_tmp("ggaze-runner-XXXXXX", &e); + g_assert_no_error(e); + + /* Sentinel that would be created if the ';' in the name were executed. */ + char *sentinel_p = g_build_filename(d, "PWNED", NULL); + GFile *sentinel = g_file_new_for_path(sentinel_p); + + /* Hostile filename: ;touch <sentinel>; — if unquoted this runs touch. */ + char *name = g_strdup_printf(";touch %s;", sentinel_p); + char *fp = g_build_filename(d, name, NULL); + GFile *f = g_file_new_for_path(fp); + g_file_replace_contents(f, "x", 1, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, NULL); + GFile *dd = g_file_new_for_path(d); + + Runner *r = runner_new(); + RunnerScript s = {"true", "true %f"}; + run_and_wait(r, f, dd, &s); + g_assert_cmpint(g_exit_code, ==, 0); + /* The sentinel must NOT exist: the ';' was quoted, not executed. */ + g_assert_false(g_file_query_exists(sentinel, NULL)); + + runner_delete(r); + g_object_unref(sentinel); + g_object_unref(f); + g_object_unref(dd); + g_free(sentinel_p); + g_free(name); + g_free(fp); + GFile *ddd = g_file_new_for_path(d); + GFileEnumerator *en = g_file_enumerate_children( + ddd, "standard::name", G_FILE_QUERY_INFO_NONE, NULL, NULL); + if (en) { + GFileInfo *i; + while ((i = g_file_enumerator_next_file(en, NULL, NULL))) { + GFile *c = g_file_get_child(ddd, g_file_info_get_name(i)); + g_file_delete(c, NULL, NULL); + g_object_unref(c); + g_object_unref(i); + } + g_object_unref(en); + } + g_file_delete(ddd, NULL, NULL); + g_object_unref(ddd); + g_free(d); +} + +/* Run a command that writes to a temp outfile, wait, then read the outfile + * back and compare its contents to c_expected. Cleans up the outfile. */ +static void +_run_and_check_output(const char *c_command, const char *c_expected) { + GError *e = NULL; + char *d = g_dir_make_tmp("ggaze-runner-XXXXXX", &e); + g_assert_no_error(e); + char *out_p = g_build_filename(d, "out.txt", NULL); + GFile *dd = g_file_new_for_path(d); + + /* Interpolate the outfile path via %s wrapped in literal single quotes, + * e.g. `printf 'hello world\n' > '/tmp/.../out.txt'`. Temp paths from + * g_dir_make_tmp contain no quotes, so this is safe for the fixtures. */ + char *cmd = g_strdup_printf(c_command, out_p); + + Runner *r = runner_new(); + RunnerScript s = {"obs", cmd}; + run_and_wait(r, NULL, dd, &s); + g_assert_cmpint(g_exit_code, ==, 0); + + char *contents = NULL; + gsize len = 0; + g_assert_true(g_file_get_contents(out_p, &contents, &len, &e)); + g_assert_no_error(e); + g_assert_cmpstr(contents, ==, c_expected); + g_free(contents); + + g_free(cmd); + runner_delete(r); + g_object_unref(dd); + g_free(out_p); + GFile *ddd = g_file_new_for_path(d); + GFileEnumerator *en = g_file_enumerate_children( + ddd, "standard::name", G_FILE_QUERY_INFO_NONE, NULL, NULL); + if (en) { + GFileInfo *i; + while ((i = g_file_enumerator_next_file(en, NULL, NULL))) { + GFile *c = g_file_get_child(ddd, g_file_info_get_name(i)); + g_file_delete(c, NULL, NULL); + g_object_unref(c); + g_object_unref(i); + } + g_object_unref(en); + } + g_file_delete(ddd, NULL, NULL); + g_object_unref(ddd); + g_free(d); +} + +/* Regression: a multi-word command must reach sh -c intact. The buggy + * g_shell_parse_argv code fed sh -c only the first word ("printf"), so the + * outfile would never contain "hello world". */ +static void +test_multi_word(void) { + _run_and_check_output("printf 'hello world\\n' > '%s'", "hello world\n"); +} + +/* Pipeline: '|' must be parsed by sh, not split into argv words. */ +static void +test_pipeline(void) { + _run_and_check_output("echo pipeline | cat > '%s'", "pipeline\n"); +} + +/* Redirection: '>' must be interpreted by the shell. */ +static void +test_redirection(void) { + _run_and_check_output("echo redir > '%s'", "redir\n"); +} + +/* Multiple space-separated arguments survive intact. */ +static void +test_spaces_in_args(void) { + _run_and_check_output("printf 'a b c\\n' > '%s'", "a b c\n"); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/runner/true_exit_zero", test_true_exit_zero); g_test_add_func("/runner/false_exit_nonzero", test_false_exit_nonzero); g_test_add_func("/runner/injection_guard", test_injection_guard); + g_test_add_func("/runner/hostile_filename", test_hostile_filename); + g_test_add_func("/runner/multi_word", test_multi_word); + g_test_add_func("/runner/pipeline", test_pipeline); + g_test_add_func("/runner/redirection", test_redirection); + g_test_add_func("/runner/spaces_in_args", test_spaces_in_args); return g_test_run(); -}
\ No newline at end of file +} |
