summaryrefslogtreecommitdiff
path: root/tests/test_runner.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_runner.c')
-rw-r--r--tests/test_runner.c135
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
+}