labfy-investigation/tests/test_tool_process.c
2026-07-17 15:42:30 +02:00

1397 lines
25 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* @file test_tool_process.c
* @brief Tests unitaires de lexécution sécurisée des outils externes.
******************************************************************************/
#include "core/tool_process.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <string.h>
#include <sys/stat.h>
#include <signal.h>
/**
* @struct ToolProcessFixture
* @brief Environnement temporaire utilisé par les tests.
*/
typedef struct
{
char *temporary_directory;
char *tool_path;
char *injection_path;
} ToolProcessFixture;
/**
* @struct ToolProcessCancellationData
* @brief Données transmises au thread chargé d'annuler une exécution.
*/
typedef struct
{
GCancellable *cancellable;
guint64 delay_microseconds;
} ToolProcessCancellationData;
/**
* @brief Annule une opération après un court délai.
*
* @param user_data Pointeur vers ToolProcessCancellationData.
*
* @return Toujours NULL.
*/
static gpointer test_tool_process_cancel_after_delay(
gpointer user_data
)
{
ToolProcessCancellationData *cancellation_data =
user_data;
if (cancellation_data == NULL ||
cancellation_data->cancellable == NULL)
{
return NULL;
}
g_usleep(
cancellation_data->delay_microseconds
);
g_cancellable_cancel(
cancellation_data->cancellable
);
return NULL;
}
/**
* @brief Crée un script exécutable utilisé comme faux outil.
*
* @param fixture Fixture du test.
* @param script_content Contenu complet du script.
*/
static void test_tool_process_write_script(
ToolProcessFixture *fixture,
const char *script_content
)
{
GError *error = NULL;
gboolean write_success = FALSE;
int chmod_result = 0;
g_assert_nonnull(
fixture
);
g_assert_nonnull(
fixture->tool_path
);
g_assert_nonnull(
script_content
);
write_success = g_file_set_contents(
fixture->tool_path,
script_content,
-1,
&error
);
g_assert_no_error(
error
);
g_assert_true(
write_success
);
chmod_result = g_chmod(
fixture->tool_path,
S_IRUSR |
S_IWUSR |
S_IXUSR
);
g_assert_cmpint(
chmod_result,
==,
0
);
}
/**
* @brief Vérifie quun GBytes contient exactement le texte attendu.
*
* La fonction ne suppose pas que les données sont terminées par '\0'.
*
* @param bytes Données à vérifier.
* @param expected_text Texte attendu.
*/
static void test_tool_process_assert_bytes_equal_text(
GBytes *bytes,
const char *expected_text
)
{
gconstpointer bytes_data = NULL;
gsize bytes_size = 0;
gsize expected_size = 0;
g_assert_nonnull(
bytes
);
g_assert_nonnull(
expected_text
);
bytes_data = g_bytes_get_data(
bytes,
&bytes_size
);
expected_size = strlen(
expected_text
);
g_assert_cmpuint(
bytes_size,
==,
expected_size
);
g_assert_cmpmem(
bytes_data,
bytes_size,
expected_text,
expected_size
);
}
/**
* @brief Prépare le répertoire temporaire dun test.
*
* @param fixture Fixture à initialiser.
* @param user_data Données inutilisées.
*/
static void test_tool_process_fixture_setup(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
GError *error = NULL;
(void) user_data;
fixture->temporary_directory =
g_dir_make_tmp(
"labfy-tool-process-XXXXXX",
&error
);
g_assert_no_error(
error
);
g_assert_nonnull(
fixture->temporary_directory
);
fixture->tool_path = g_build_filename(
fixture->temporary_directory,
"fake_tool",
NULL
);
fixture->injection_path = g_build_filename(
fixture->temporary_directory,
"injection_created",
NULL
);
}
/**
* @brief Supprime les fichiers et le dossier temporaire.
*
* @param fixture Fixture à nettoyer.
* @param user_data Données inutilisées.
*/
static void test_tool_process_fixture_teardown(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
(void) user_data;
if (fixture->tool_path != NULL)
{
g_remove(
fixture->tool_path
);
}
if (fixture->injection_path != NULL)
{
g_remove(
fixture->injection_path
);
}
if (fixture->temporary_directory != NULL)
{
g_rmdir(
fixture->temporary_directory
);
}
g_clear_pointer(
&fixture->tool_path,
g_free
);
g_clear_pointer(
&fixture->injection_path,
g_free
);
g_clear_pointer(
&fixture->temporary_directory,
g_free
);
}
/**
* @brief Vérifie le refus des arguments invalides.
*/
static void test_tool_process_invalid_arguments(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
ToolProcessResult *occupied_result = NULL;
GError *error = NULL;
(void) user_data;
g_assert_false(
tool_process_run(
NULL,
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_error(
error,
TOOL_PROCESS_ERROR,
TOOL_PROCESS_ERROR_INVALID_ARGUMENT
);
g_assert_null(
result
);
g_clear_error(
&error
);
g_assert_false(
tool_process_run(
"",
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_error(
error,
TOOL_PROCESS_ERROR,
TOOL_PROCESS_ERROR_INVALID_ARGUMENT
);
g_clear_error(
&error
);
g_assert_false(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
NULL,
NULL,
&error
)
);
g_assert_error(
error,
TOOL_PROCESS_ERROR,
TOOL_PROCESS_ERROR_INVALID_ARGUMENT
);
g_clear_error(
&error
);
/*
* out_result doit pointer vers NULL avant lappel.
*
* Ce pointeur sert uniquement de marqueur et nest jamais déréférencé.
*/
occupied_result =
(ToolProcessResult *) fixture;
g_assert_false(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
NULL,
&occupied_result,
&error
)
);
g_assert_error(
error,
TOOL_PROCESS_ERROR,
TOOL_PROCESS_ERROR_INVALID_ARGUMENT
);
g_clear_error(
&error
);
g_assert_false(
tool_process_run(
fixture->tool_path,
NULL,
"",
NULL,
&result,
&error
)
);
g_assert_error(
error,
TOOL_PROCESS_ERROR,
TOOL_PROCESS_ERROR_INVALID_ARGUMENT
);
g_clear_error(
&error
);
}
/**
* @brief Vérifie lexécution dun outil sans argument.
*/
static void test_tool_process_no_arguments(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GBytes *stdout_bytes = NULL;
GBytes *stderr_bytes = NULL;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"printf 'outil-execute'\n"
);
g_assert_true(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_no_error(
error
);
g_assert_nonnull(
result
);
g_assert_true(
tool_process_result_exited_normally(
result
)
);
g_assert_false(
tool_process_result_was_signaled(
result
)
);
g_assert_cmpint(
tool_process_result_get_exit_status(
result
),
==,
0
);
g_assert_cmpint(
tool_process_result_get_termination_signal(
result
),
==,
0
);
g_assert_true(
tool_process_result_is_success(
result
)
);
stdout_bytes =
tool_process_result_ref_stdout(
result
);
stderr_bytes =
tool_process_result_ref_stderr(
result
);
test_tool_process_assert_bytes_equal_text(
stdout_bytes,
"outil-execute"
);
test_tool_process_assert_bytes_equal_text(
stderr_bytes,
""
);
g_bytes_unref(
stdout_bytes
);
g_bytes_unref(
stderr_bytes
);
tool_process_result_free(
result
);
}
/**
* @brief Vérifie que les arguments ne sont pas interprétés par un shell.
*/
static void test_tool_process_literal_arguments(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GBytes *stdout_bytes = NULL;
char *injection_argument = NULL;
char *expected_output = NULL;
const char *arguments[6];
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"for argument in \"$@\"\n"
"do\n"
" printf '%s\\n' \"$argument\"\n"
"done\n"
);
injection_argument = g_strdup_printf(
"$(touch %s)",
fixture->injection_path
);
arguments[0] = "example.org";
arguments[1] = "nom avec espaces";
arguments[2] = injection_argument;
arguments[3] = "; echo danger";
arguments[4] = "*";
arguments[5] = NULL;
expected_output = g_strdup_printf(
"example.org\n"
"nom avec espaces\n"
"%s\n"
"; echo danger\n"
"*\n",
injection_argument
);
g_assert_false(
g_file_test(
fixture->injection_path,
G_FILE_TEST_EXISTS
)
);
g_assert_true(
tool_process_run(
fixture->tool_path,
arguments,
NULL,
NULL,
&result,
&error
)
);
g_assert_no_error(
error
);
g_assert_true(
tool_process_result_is_success(
result
)
);
stdout_bytes =
tool_process_result_ref_stdout(
result
);
test_tool_process_assert_bytes_equal_text(
stdout_bytes,
expected_output
);
/*
* Si un shell avait interprété largument, ce fichier existerait.
*/
g_assert_false(
g_file_test(
fixture->injection_path,
G_FILE_TEST_EXISTS
)
);
g_bytes_unref(
stdout_bytes
);
tool_process_result_free(
result
);
g_free(
expected_output
);
g_free(
injection_argument
);
}
/**
* @brief Vérifie la séparation de stdout et stderr.
*/
static void test_tool_process_stdout_stderr(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GBytes *stdout_bytes = NULL;
GBytes *stderr_bytes = NULL;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"printf 'message-out'\n"
"printf 'message-err' >&2\n"
);
g_assert_true(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_no_error(
error
);
stdout_bytes =
tool_process_result_ref_stdout(
result
);
stderr_bytes =
tool_process_result_ref_stderr(
result
);
test_tool_process_assert_bytes_equal_text(
stdout_bytes,
"message-out"
);
test_tool_process_assert_bytes_equal_text(
stderr_bytes,
"message-err"
);
g_bytes_unref(
stdout_bytes
);
g_bytes_unref(
stderr_bytes
);
tool_process_result_free(
result
);
}
/**
* @brief Vérifie quun code non nul produit tout de même un résultat.
*/
static void test_tool_process_nonzero_exit(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"printf 'echec-fonctionnel' >&2\n"
"exit 7\n"
);
g_assert_true(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_no_error(
error
);
g_assert_nonnull(
result
);
g_assert_true(
tool_process_result_exited_normally(
result
)
);
g_assert_cmpint(
tool_process_result_get_exit_status(
result
),
==,
7
);
g_assert_false(
tool_process_result_is_success(
result
)
);
tool_process_result_free(
result
);
}
/**
* @brief Vérifie lerreur produite par un exécutable inexistant.
*/
static void test_tool_process_missing_executable(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
char *missing_path = NULL;
GError *error = NULL;
(void) user_data;
missing_path = g_build_filename(
fixture->temporary_directory,
"tool_that_does_not_exist",
NULL
);
g_assert_false(
tool_process_run(
missing_path,
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_error(
error,
TOOL_PROCESS_ERROR,
TOOL_PROCESS_ERROR_SPAWN
);
g_assert_null(
result
);
g_clear_error(
&error
);
g_free(
missing_path
);
}
/**
* @brief Vérifie l'utilisation du dossier de travail demandé.
*/
static void test_tool_process_working_directory(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GBytes *stdout_bytes = NULL;
char *canonical_directory = NULL;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"printf '%s' \"$PWD\"\n"
);
g_assert_true(
tool_process_run(
fixture->tool_path,
NULL,
fixture->temporary_directory,
NULL,
&result,
&error
)
);
g_assert_no_error(
error
);
g_assert_nonnull(
result
);
canonical_directory = g_canonicalize_filename(
fixture->temporary_directory,
NULL
);
stdout_bytes =
tool_process_result_ref_stdout(
result
);
test_tool_process_assert_bytes_equal_text(
stdout_bytes,
canonical_directory
);
g_bytes_unref(
stdout_bytes
);
g_free(
canonical_directory
);
tool_process_result_free(
result
);
}
/**
* @brief Vérifie une annulation demandée avant le lancement.
*/
static void test_tool_process_cancelled_before_start(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GCancellable *cancellable = NULL;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"exit 0\n"
);
cancellable = g_cancellable_new();
g_cancellable_cancel(
cancellable
);
g_assert_false(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
cancellable,
&result,
&error
)
);
g_assert_error(
error,
TOOL_PROCESS_ERROR,
TOOL_PROCESS_ERROR_CANCELLED
);
g_assert_null(
result
);
g_clear_error(
&error
);
g_object_unref(
cancellable
);
}
/**
* @brief Vérifie l'annulation d'un processus en cours.
*/
static void test_tool_process_cancelled_during_run(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GCancellable *cancellable = NULL;
GThread *cancellation_thread = NULL;
ToolProcessCancellationData cancellation_data;
gint64 start_time = 0;
gint64 end_time = 0;
gint64 elapsed_microseconds = 0;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"while :\n"
"do\n"
" :\n"
"done\n"
);
cancellable = g_cancellable_new();
cancellation_data.cancellable =
cancellable;
cancellation_data.delay_microseconds =
200000;
cancellation_thread = g_thread_new(
"tool-process-canceller",
test_tool_process_cancel_after_delay,
&cancellation_data
);
g_assert_nonnull(
cancellation_thread
);
start_time = g_get_monotonic_time();
g_assert_false(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
cancellable,
&result,
&error
)
);
end_time = g_get_monotonic_time();
g_thread_join(
cancellation_thread
);
elapsed_microseconds =
end_time - start_time;
g_assert_error(
error,
TOOL_PROCESS_ERROR,
TOOL_PROCESS_ERROR_CANCELLED
);
g_assert_null(
result
);
/*
* L'exécution doit être interrompue bien avant plusieurs secondes.
*/
g_assert_cmpint(
elapsed_microseconds,
<,
3000000
);
g_clear_error(
&error
);
g_object_unref(
cancellable
);
}
/**
* @brief Vérifie qu'une exécution sans sortie produit des GBytes vides.
*/
static void test_tool_process_empty_output(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GBytes *stdout_bytes = NULL;
GBytes *stderr_bytes = NULL;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"exit 0\n"
);
g_assert_true(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_no_error(
error
);
stdout_bytes =
tool_process_result_ref_stdout(
result
);
stderr_bytes =
tool_process_result_ref_stderr(
result
);
g_assert_nonnull(
stdout_bytes
);
g_assert_nonnull(
stderr_bytes
);
g_assert_cmpuint(
g_bytes_get_size(
stdout_bytes
),
==,
0
);
g_assert_cmpuint(
g_bytes_get_size(
stderr_bytes
),
==,
0
);
g_bytes_unref(
stdout_bytes
);
g_bytes_unref(
stderr_bytes
);
tool_process_result_free(
result
);
}
/**
* @brief Vérifie la conservation d'une sortie non UTF-8.
*/
static void test_tool_process_non_utf8_output(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GBytes *stdout_bytes = NULL;
gconstpointer stdout_data = NULL;
const guint8 *stdout_octets = NULL;
gsize stdout_size = 0;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"printf '\\377'\n"
);
g_assert_true(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_no_error(
error
);
stdout_bytes =
tool_process_result_ref_stdout(
result
);
g_assert_nonnull(
stdout_bytes
);
stdout_data = g_bytes_get_data(
stdout_bytes,
&stdout_size
);
stdout_octets = stdout_data;
g_assert_cmpuint(
stdout_size,
==,
1
);
g_assert_nonnull(
stdout_octets
);
g_assert_cmpuint(
stdout_octets[0],
==,
0xFF
);
g_bytes_unref(
stdout_bytes
);
tool_process_result_free(
result
);
}
/**
* @brief Vérifie la représentation d'une terminaison par signal.
*/
static void test_tool_process_signaled(
ToolProcessFixture *fixture,
gconstpointer user_data
)
{
ToolProcessResult *result = NULL;
GError *error = NULL;
(void) user_data;
test_tool_process_write_script(
fixture,
"#!/bin/sh\n"
"kill -TERM $$\n"
);
g_assert_true(
tool_process_run(
fixture->tool_path,
NULL,
NULL,
NULL,
&result,
&error
)
);
g_assert_no_error(
error
);
g_assert_nonnull(
result
);
g_assert_false(
tool_process_result_exited_normally(
result
)
);
g_assert_true(
tool_process_result_was_signaled(
result
)
);
g_assert_cmpint(
tool_process_result_get_exit_status(
result
),
==,
-1
);
g_assert_cmpint(
tool_process_result_get_termination_signal(
result
),
==,
SIGTERM
);
g_assert_false(
tool_process_result_is_success(
result
)
);
tool_process_result_free(
result
);
}
int main(
int argc,
char **argv
)
{
g_test_init(
&argc,
&argv,
NULL
);
g_test_add(
"/tool_process/invalid_arguments",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_invalid_arguments,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/no_arguments",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_no_arguments,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/literal_arguments",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_literal_arguments,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/stdout_stderr",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_stdout_stderr,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/nonzero_exit",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_nonzero_exit,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/missing_executable",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_missing_executable,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/working_directory",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_working_directory,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/cancelled_before_start",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_cancelled_before_start,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/cancelled_during_run",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_cancelled_during_run,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/empty_output",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_empty_output,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/non_utf8_output",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_non_utf8_output,
test_tool_process_fixture_teardown
);
g_test_add(
"/tool_process/signaled",
ToolProcessFixture,
NULL,
test_tool_process_fixture_setup,
test_tool_process_signaled,
test_tool_process_fixture_teardown
);
return g_test_run();
}