1129 lines
20 KiB
Markdown
1129 lines
20 KiB
Markdown
# TICKET-037 — Exécution sécurisée des outils externes avec GSubprocess
|
||
|
||
## Statut
|
||
|
||
À faire
|
||
|
||
## Priorité
|
||
|
||
Haute
|
||
|
||
## Objectif
|
||
|
||
Créer une abstraction C centralisée permettant à Labfy Investigation d’exécuter un outil externe de manière contrôlée avec `GSubprocess`, sans passer par un shell.
|
||
|
||
Le module devra :
|
||
|
||
- recevoir le chemin d’un exécutable ;
|
||
- recevoir une liste d’arguments séparés ;
|
||
- lancer directement le programme ;
|
||
- capturer intégralement `stdout` et `stderr` ;
|
||
- conserver le code de sortie ;
|
||
- distinguer une fin normale d’une terminaison par signal ;
|
||
- permettre l’annulation avec `GCancellable` ;
|
||
- restituer un résultat structuré ;
|
||
- rester indépendant de GTK ;
|
||
- pouvoir être utilisé dans un worker `BackgroundTask`.
|
||
|
||
---
|
||
|
||
## Contexte
|
||
|
||
Le ticket #036 fournit désormais un registre capable de détecter les outils présents sur la machine et de mémoriser leur chemin.
|
||
|
||
Le prochain besoin est d’exécuter ces outils sans introduire de vulnérabilité liée à une commande shell.
|
||
|
||
Labfy Investigation devra progressivement lancer des outils tels que :
|
||
|
||
```text
|
||
dig
|
||
curl
|
||
openssl
|
||
whois
|
||
exiftool
|
||
```
|
||
|
||
Les arguments pourront contenir :
|
||
|
||
- des noms de domaines ;
|
||
- des chemins de fichiers ;
|
||
- des URL ;
|
||
- des options ;
|
||
- des espaces ;
|
||
- des caractères normalement interprétés par un shell.
|
||
|
||
Ces valeurs ne doivent jamais être concaténées dans une commande textuelle.
|
||
|
||
---
|
||
|
||
## Principe de sécurité
|
||
|
||
Le module doit lancer l’exécutable et ses arguments sous forme de tableau.
|
||
|
||
Exemple conceptuel correct :
|
||
|
||
```c
|
||
const char *arguments[] =
|
||
{
|
||
"+short",
|
||
"example.org",
|
||
NULL
|
||
};
|
||
```
|
||
|
||
Exemple interdit :
|
||
|
||
```c
|
||
char *command = g_strdup_printf(
|
||
"dig +short %s",
|
||
domain_name
|
||
);
|
||
|
||
system(command);
|
||
```
|
||
|
||
Les API suivantes sont interdites dans le code de production :
|
||
|
||
```text
|
||
system()
|
||
popen()
|
||
execl("/bin/sh", ...)
|
||
sh -c
|
||
bash -c
|
||
g_spawn_command_line_sync()
|
||
g_spawn_command_line_async()
|
||
```
|
||
|
||
Le module doit utiliser `GSubprocess` ou `GSubprocessLauncher` avec un vecteur d’arguments.
|
||
|
||
---
|
||
|
||
## Périmètre
|
||
|
||
Ce ticket doit fournir :
|
||
|
||
1. un module autonome `tool_process` ;
|
||
2. une structure opaque `ToolProcessResult` ;
|
||
3. une fonction synchrone et annulable d’exécution ;
|
||
4. la capture brute de `stdout` ;
|
||
5. la capture brute de `stderr` ;
|
||
6. le code de sortie ;
|
||
7. l’état de fin normale ou par signal ;
|
||
8. la conservation du signal de terminaison lorsqu’il est disponible ;
|
||
9. la gestion d’un dossier de travail facultatif ;
|
||
10. une distinction claire entre :
|
||
- une erreur de lancement ;
|
||
- une erreur de communication ;
|
||
- une annulation ;
|
||
- un programme ayant simplement retourné un code différent de zéro ;
|
||
11. des tests unitaires avec de faux outils temporaires ;
|
||
12. l’intégration au `Makefile`.
|
||
|
||
---
|
||
|
||
## Hors périmètre
|
||
|
||
Ce ticket ne doit pas encore :
|
||
|
||
- créer une interface GTK ;
|
||
- lancer automatiquement les outils du registre ;
|
||
- analyser la sortie de `dig`, `curl`, `openssl` ou d’un autre outil ;
|
||
- déterminer la version d’un outil ;
|
||
- ajouter une limite de durée automatique ;
|
||
- gérer un pool de processus ;
|
||
- gérer une file d’attente ;
|
||
- enregistrer les sorties dans SQLite ;
|
||
- créer une preuve ou une observation OSINT ;
|
||
- modifier l’environnement complet du processus ;
|
||
- afficher la progression détaillée d’une commande ;
|
||
- ajouter un pseudo-terminal ;
|
||
- exécuter une commande à travers un shell.
|
||
|
||
L’intégration avec `BackgroundTask` sera effectuée après validation de cette abstraction.
|
||
|
||
---
|
||
|
||
## Fichiers attendus
|
||
|
||
```text
|
||
include/core/tool_process.h
|
||
src/core/tool_process.c
|
||
tests/test_tool_process.c
|
||
```
|
||
|
||
Le `Makefile` devra compiler le module dans l’application principale et fournir une cible :
|
||
|
||
```text
|
||
tests/test_tool_process
|
||
```
|
||
|
||
---
|
||
|
||
## Contraintes générales
|
||
|
||
- C17.
|
||
- GLib et GIO.
|
||
- Aucune dépendance GTK.
|
||
- Compilation stricte :
|
||
|
||
```text
|
||
-Wall -Wextra -Wpedantic -Werror
|
||
```
|
||
|
||
- Structure publique opaque.
|
||
- Fonctions préfixées par :
|
||
|
||
```text
|
||
tool_process_
|
||
tool_process_result_
|
||
```
|
||
|
||
- Aucun état global mutable.
|
||
- Aucun shell.
|
||
- Aucun chemin d’outil codé en dur dans le module.
|
||
- Les arguments doivent être transmis séparément.
|
||
- Les sorties doivent être conservées sous forme d’octets bruts.
|
||
- Les données reçues doivent être copiées ou référencées avec une propriété clairement documentée.
|
||
|
||
---
|
||
|
||
## Modèle public
|
||
|
||
### ToolProcessResult
|
||
|
||
```c
|
||
typedef struct ToolProcessResult ToolProcessResult;
|
||
```
|
||
|
||
La structure doit rester opaque.
|
||
|
||
Elle doit contenir au minimum :
|
||
|
||
```text
|
||
stdout_bytes
|
||
stderr_bytes
|
||
exited_normally
|
||
exit_status
|
||
was_signaled
|
||
termination_signal
|
||
```
|
||
|
||
### Propriété
|
||
|
||
Le résultat retourné appartient à l’appelant.
|
||
|
||
Il doit être libéré avec :
|
||
|
||
```c
|
||
void tool_process_result_free(
|
||
ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
Les sorties doivent être conservées avec `GBytes`.
|
||
|
||
---
|
||
|
||
## Domaine d’erreur
|
||
|
||
Créer :
|
||
|
||
```c
|
||
#define TOOL_PROCESS_ERROR \
|
||
tool_process_error_quark()
|
||
```
|
||
|
||
Énumération minimale :
|
||
|
||
```c
|
||
typedef enum
|
||
{
|
||
TOOL_PROCESS_ERROR_INVALID_ARGUMENT,
|
||
TOOL_PROCESS_ERROR_SPAWN,
|
||
TOOL_PROCESS_ERROR_COMMUNICATION,
|
||
TOOL_PROCESS_ERROR_CANCELLED,
|
||
TOOL_PROCESS_ERROR_INVALID_RESULT
|
||
} ToolProcessError;
|
||
```
|
||
|
||
Fonction :
|
||
|
||
```c
|
||
GQuark tool_process_error_quark(void);
|
||
```
|
||
|
||
### Règle importante
|
||
|
||
Un programme qui se lance correctement puis retourne :
|
||
|
||
```text
|
||
exit 1
|
||
exit 2
|
||
exit 127
|
||
```
|
||
|
||
ne constitue pas une erreur de l’API.
|
||
|
||
Dans ce cas :
|
||
|
||
```text
|
||
tool_process_run() retourne TRUE
|
||
```
|
||
|
||
et le code de sortie est conservé dans `ToolProcessResult`.
|
||
|
||
La fonction ne retourne `FALSE` que lorsque le module n’a pas pu mener l’exécution jusqu’à l’obtention d’un résultat exploitable.
|
||
|
||
---
|
||
|
||
## API publique attendue
|
||
|
||
L’API exacte peut être ajustée si une meilleure conception est justifiée, mais elle doit couvrir les opérations suivantes.
|
||
|
||
### Exécution
|
||
|
||
```c
|
||
gboolean tool_process_run(
|
||
const char *executable_path,
|
||
const char *const arguments[],
|
||
const char *working_directory,
|
||
GCancellable *cancellable,
|
||
ToolProcessResult **out_result,
|
||
GError **error
|
||
);
|
||
```
|
||
|
||
### Paramètres
|
||
|
||
#### executable_path
|
||
|
||
Chemin de l’exécutable.
|
||
|
||
Le module doit accepter un chemin absolu détecté par `ToolRegistry`.
|
||
|
||
Le chemin doit être :
|
||
|
||
- non `NULL` ;
|
||
- non vide.
|
||
|
||
Le module ne doit pas reconstruire une commande textuelle.
|
||
|
||
#### arguments
|
||
|
||
Tableau terminé par `NULL`.
|
||
|
||
Ce tableau contient uniquement les arguments placés après `argv[0]`.
|
||
|
||
Exemple :
|
||
|
||
```c
|
||
const char *arguments[] =
|
||
{
|
||
"+short",
|
||
"example.org",
|
||
NULL
|
||
};
|
||
```
|
||
|
||
`arguments` peut être `NULL`, ce qui signifie aucun argument supplémentaire.
|
||
|
||
Le module doit construire en interne un vecteur de ce type :
|
||
|
||
```text
|
||
argv[0] = executable_path
|
||
argv[1] = arguments[0]
|
||
argv[2] = arguments[1]
|
||
...
|
||
argv[n] = NULL
|
||
```
|
||
|
||
Chaque argument doit rester un élément distinct.
|
||
|
||
#### working_directory
|
||
|
||
Dossier de travail facultatif.
|
||
|
||
- `NULL` signifie utiliser le dossier courant hérité ;
|
||
- une chaîne non vide demande l’utilisation de ce dossier ;
|
||
- une chaîne vide est invalide.
|
||
|
||
Utiliser `GSubprocessLauncher` lorsqu’un dossier de travail est fourni.
|
||
|
||
#### cancellable
|
||
|
||
Objet d’annulation facultatif.
|
||
|
||
- `NULL` signifie que l’appel n’est pas annulable ;
|
||
- un objet déjà annulé doit provoquer un échec avec :
|
||
- `TOOL_PROCESS_ERROR_CANCELLED`.
|
||
|
||
#### out_result
|
||
|
||
Doit être non `NULL`.
|
||
|
||
Avant l’appel :
|
||
|
||
```c
|
||
*out_result == NULL
|
||
```
|
||
|
||
En cas de succès :
|
||
|
||
```c
|
||
*out_result != NULL
|
||
```
|
||
|
||
En cas d’échec :
|
||
|
||
```c
|
||
*out_result == NULL
|
||
```
|
||
|
||
#### error
|
||
|
||
Respecter la convention GLib :
|
||
|
||
```c
|
||
error == NULL || *error == NULL
|
||
```
|
||
|
||
---
|
||
|
||
## Utilisation de GSubprocess
|
||
|
||
Créer le processus avec les drapeaux :
|
||
|
||
```c
|
||
G_SUBPROCESS_FLAGS_STDOUT_PIPE
|
||
G_SUBPROCESS_FLAGS_STDERR_PIPE
|
||
```
|
||
|
||
La capture recommandée est :
|
||
|
||
```c
|
||
g_subprocess_communicate()
|
||
```
|
||
|
||
Cette API permet de récupérer des `GBytes` et évite de supposer que les sorties sont valides en UTF-8.
|
||
|
||
Le module ne doit pas utiliser exclusivement :
|
||
|
||
```c
|
||
g_subprocess_communicate_utf8()
|
||
```
|
||
|
||
car une sortie brute peut contenir des octets non UTF-8.
|
||
|
||
---
|
||
|
||
## Données envoyées à stdin
|
||
|
||
Dans ce ticket, aucun contenu n’est envoyé à l’entrée standard.
|
||
|
||
La communication doit transmettre :
|
||
|
||
```c
|
||
stdin_buf = NULL
|
||
```
|
||
|
||
Une gestion explicite de stdin pourra être ajoutée plus tard.
|
||
|
||
---
|
||
|
||
## Construction du vecteur d’arguments
|
||
|
||
L’implémentation peut utiliser :
|
||
|
||
```c
|
||
GPtrArray
|
||
```
|
||
|
||
Exemple conceptuel :
|
||
|
||
```c
|
||
GPtrArray *argv_builder = NULL;
|
||
|
||
argv_builder = g_ptr_array_new();
|
||
|
||
g_ptr_array_add(
|
||
argv_builder,
|
||
(gpointer) executable_path
|
||
);
|
||
|
||
/* Ajout de chaque argument séparément. */
|
||
|
||
g_ptr_array_add(
|
||
argv_builder,
|
||
NULL
|
||
);
|
||
```
|
||
|
||
Le module doit décider clairement s’il duplique les chaînes ou s’il les emprunte uniquement pendant l’appel.
|
||
|
||
Comme `g_subprocess_launcher_spawnv()` consomme le tableau pendant l’appel de création, les chaînes peuvent être empruntées si leur durée de vie couvre cet appel.
|
||
|
||
Aucune fonction ne doit concaténer les arguments.
|
||
|
||
---
|
||
|
||
## Résultat d’exécution
|
||
|
||
### Cycle de vie
|
||
|
||
```c
|
||
void tool_process_result_free(
|
||
ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
La fonction doit accepter `NULL`.
|
||
|
||
### Sortie standard
|
||
|
||
```c
|
||
GBytes *tool_process_result_ref_stdout(
|
||
const ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
Retourne une nouvelle référence.
|
||
|
||
L’appelant doit la libérer avec :
|
||
|
||
```c
|
||
g_bytes_unref()
|
||
```
|
||
|
||
### Sortie d’erreur
|
||
|
||
```c
|
||
GBytes *tool_process_result_ref_stderr(
|
||
const ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
Retourne une nouvelle référence.
|
||
|
||
### Fin normale
|
||
|
||
```c
|
||
gboolean tool_process_result_exited_normally(
|
||
const ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
### Code de sortie
|
||
|
||
```c
|
||
int tool_process_result_get_exit_status(
|
||
const ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
Règles recommandées :
|
||
|
||
- retourne le code réel si le programme s’est terminé normalement ;
|
||
- retourne `-1` si le résultat est `NULL` ou si le processus ne s’est pas terminé normalement.
|
||
|
||
### Terminaison par signal
|
||
|
||
```c
|
||
gboolean tool_process_result_was_signaled(
|
||
const ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
```c
|
||
int tool_process_result_get_termination_signal(
|
||
const ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
Règles recommandées :
|
||
|
||
- retourne le signal réel si disponible ;
|
||
- retourne `0` dans les autres cas.
|
||
|
||
### Succès fonctionnel
|
||
|
||
Ajouter une fonction pratique :
|
||
|
||
```c
|
||
gboolean tool_process_result_is_success(
|
||
const ToolProcessResult *result
|
||
);
|
||
```
|
||
|
||
Elle retourne `TRUE` uniquement lorsque :
|
||
|
||
```text
|
||
exited_normally == TRUE
|
||
exit_status == 0
|
||
```
|
||
|
||
Cette fonction ne doit pas confondre le succès de l’API et le succès du programme lancé.
|
||
|
||
---
|
||
|
||
## Gestion des sorties vides
|
||
|
||
Même lorsqu’un programme n’écrit rien, le résultat doit rester exploitable.
|
||
|
||
Les choix acceptables sont :
|
||
|
||
```text
|
||
GBytes vide
|
||
```
|
||
|
||
ou :
|
||
|
||
```text
|
||
NULL documenté comme sortie vide
|
||
```
|
||
|
||
La solution recommandée est de conserver un `GBytes` vide afin de simplifier les appelants.
|
||
|
||
Les deux flux doivent être indépendants.
|
||
|
||
---
|
||
|
||
## Gestion de l’annulation
|
||
|
||
Lorsqu’un `GCancellable` est annulé pendant :
|
||
|
||
```c
|
||
g_subprocess_communicate()
|
||
```
|
||
|
||
le module doit :
|
||
|
||
1. interrompre l’attente ;
|
||
2. forcer l’arrêt du processus si nécessaire avec :
|
||
```c
|
||
g_subprocess_force_exit()
|
||
```
|
||
3. attendre ou finaliser proprement l’objet `GSubprocess` selon le comportement GLib ;
|
||
4. ne retourner aucun résultat partiel ;
|
||
5. retourner `FALSE` ;
|
||
6. produire :
|
||
```text
|
||
TOOL_PROCESS_ERROR_CANCELLED
|
||
```
|
||
|
||
Le module ne doit pas laisser de processus enfant actif après l’annulation.
|
||
|
||
---
|
||
|
||
## Nettoyage obligatoire
|
||
|
||
Tous les chemins d’erreur doivent libérer :
|
||
|
||
- le tableau d’arguments ;
|
||
- le `GSubprocessLauncher` ;
|
||
- le `GSubprocess` ;
|
||
- les `GBytes` temporaires ;
|
||
- le résultat partiellement construit ;
|
||
- toute erreur intermédiaire.
|
||
|
||
L’implémentation doit utiliser lorsque cela améliore la lisibilité :
|
||
|
||
```c
|
||
g_autoptr()
|
||
g_clear_object()
|
||
g_clear_pointer()
|
||
```
|
||
|
||
Le style doit rester cohérent avec le reste du projet.
|
||
|
||
---
|
||
|
||
## Comportement attendu
|
||
|
||
### Exécutable introuvable
|
||
|
||
```text
|
||
retour : FALSE
|
||
résultat : NULL
|
||
erreur : TOOL_PROCESS_ERROR_SPAWN
|
||
```
|
||
|
||
### Exécutable valide, code zéro
|
||
|
||
```text
|
||
retour : TRUE
|
||
résultat : non NULL
|
||
is_success : TRUE
|
||
exit_status : 0
|
||
```
|
||
|
||
### Exécutable valide, code non nul
|
||
|
||
```text
|
||
retour : TRUE
|
||
résultat : non NULL
|
||
is_success : FALSE
|
||
exit_status : code réel
|
||
```
|
||
|
||
### Annulation
|
||
|
||
```text
|
||
retour : FALSE
|
||
résultat : NULL
|
||
erreur : TOOL_PROCESS_ERROR_CANCELLED
|
||
```
|
||
|
||
### Sortie invalide UTF-8
|
||
|
||
```text
|
||
retour : TRUE
|
||
stdout brut conservé dans GBytes
|
||
```
|
||
|
||
---
|
||
|
||
## Tests unitaires obligatoires
|
||
|
||
Les tests doivent créer leurs propres faux exécutables temporaires.
|
||
|
||
Ils ne doivent pas dépendre de :
|
||
|
||
```text
|
||
dig
|
||
curl
|
||
openssl
|
||
whois
|
||
```
|
||
|
||
Ils peuvent utiliser des scripts temporaires avec un shebang, car le projet cible Linux.
|
||
|
||
Le code du module ne doit cependant jamais lancer un shell explicitement.
|
||
|
||
---
|
||
|
||
### 1. Arguments invalides
|
||
|
||
Tester au minimum :
|
||
|
||
- `executable_path == NULL` ;
|
||
- chemin vide ;
|
||
- `out_result == NULL` ;
|
||
- `*out_result != NULL` ;
|
||
- dossier de travail vide ;
|
||
- `GError` déjà initialisé si cette convention est vérifiée par le projet.
|
||
|
||
Résultat attendu :
|
||
|
||
```text
|
||
TOOL_PROCESS_ERROR_INVALID_ARGUMENT
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Exécution sans argument
|
||
|
||
Créer un faux outil qui affiche un texte fixe.
|
||
|
||
Vérifier :
|
||
|
||
- retour `TRUE` ;
|
||
- fin normale ;
|
||
- code zéro ;
|
||
- sortie capturée ;
|
||
- `stderr` vide ;
|
||
- `is_success == TRUE`.
|
||
|
||
---
|
||
|
||
### 3. Transmission exacte des arguments
|
||
|
||
Le faux outil doit afficher chaque argument reçu sur une ligne séparée.
|
||
|
||
Tester des arguments comme :
|
||
|
||
```text
|
||
example.org
|
||
nom avec espaces
|
||
$(touch /tmp/labfy-injection)
|
||
; echo danger
|
||
*
|
||
```
|
||
|
||
Vérifier que les valeurs sont restituées littéralement.
|
||
|
||
Le test doit également vérifier qu’aucun fichier d’injection n’a été créé.
|
||
|
||
Ce test prouve qu’aucun shell n’interprète les arguments.
|
||
|
||
---
|
||
|
||
### 4. Capture séparée de stdout et stderr
|
||
|
||
Le faux outil écrit :
|
||
|
||
```text
|
||
message-out
|
||
```
|
||
|
||
dans `stdout`, et :
|
||
|
||
```text
|
||
message-err
|
||
```
|
||
|
||
dans `stderr`.
|
||
|
||
Vérifier que les deux sorties ne sont pas mélangées.
|
||
|
||
---
|
||
|
||
### 5. Code de sortie non nul
|
||
|
||
Le faux outil doit :
|
||
|
||
```text
|
||
exit 7
|
||
```
|
||
|
||
Vérifier :
|
||
|
||
- `tool_process_run() == TRUE` ;
|
||
- fin normale ;
|
||
- code de sortie `7` ;
|
||
- `is_success == FALSE`.
|
||
|
||
---
|
||
|
||
### 6. Exécutable inexistant
|
||
|
||
Utiliser un chemin temporaire inexistant.
|
||
|
||
Vérifier :
|
||
|
||
- retour `FALSE` ;
|
||
- résultat `NULL` ;
|
||
- erreur `TOOL_PROCESS_ERROR_SPAWN`.
|
||
|
||
---
|
||
|
||
### 7. Dossier de travail
|
||
|
||
Créer un dossier temporaire et un outil affichant son répertoire courant.
|
||
|
||
Exécuter avec `working_directory`.
|
||
|
||
Vérifier que la sortie correspond au chemin demandé.
|
||
|
||
---
|
||
|
||
### 8. Annulation avant le lancement
|
||
|
||
Créer un `GCancellable`, l’annuler avant l’appel, puis exécuter le faux outil.
|
||
|
||
Vérifier :
|
||
|
||
- retour `FALSE` ;
|
||
- résultat `NULL` ;
|
||
- erreur `TOOL_PROCESS_ERROR_CANCELLED`.
|
||
|
||
---
|
||
|
||
### 9. Annulation pendant l’exécution
|
||
|
||
Créer un faux outil long, par exemple :
|
||
|
||
```sh
|
||
#!/bin/sh
|
||
sleep 10
|
||
```
|
||
|
||
Lancer l’annulation depuis un second thread après un court délai.
|
||
|
||
Vérifier :
|
||
|
||
- retour `FALSE` ;
|
||
- erreur d’annulation ;
|
||
- aucun résultat ;
|
||
- durée du test nettement inférieure à dix secondes ;
|
||
- aucun processus enfant laissé actif.
|
||
|
||
Le test ne doit pas dépendre d’une boucle principale GTK.
|
||
|
||
---
|
||
|
||
### 10. Sortie vide
|
||
|
||
Créer un outil qui ne produit aucune sortie.
|
||
|
||
Vérifier que les accesseurs de sortie restent sûrs.
|
||
|
||
---
|
||
|
||
### 11. Octets non UTF-8
|
||
|
||
Créer un outil qui écrit au moins un octet non UTF-8 dans `stdout`.
|
||
|
||
Vérifier :
|
||
|
||
- retour `TRUE` ;
|
||
- récupération des octets avec `GBytes` ;
|
||
- taille exacte ;
|
||
- contenu exact.
|
||
|
||
Ce test confirme que le module n’utilise pas une API limitée au texte UTF-8.
|
||
|
||
---
|
||
|
||
### 12. Terminaison par signal
|
||
|
||
Sur Linux, créer un faux outil qui se termine lui-même par un signal.
|
||
|
||
Vérifier lorsque la plateforme le permet :
|
||
|
||
- `exited_normally == FALSE` ;
|
||
- `was_signaled == TRUE` ;
|
||
- signal non nul ;
|
||
- `exit_status == -1` ;
|
||
- `is_success == FALSE`.
|
||
|
||
Le test peut être conditionné avec les macros de plateforme appropriées.
|
||
|
||
---
|
||
|
||
## Noms de tests suggérés
|
||
|
||
```text
|
||
/tool_process/invalid_arguments
|
||
/tool_process/no_arguments
|
||
/tool_process/literal_arguments
|
||
/tool_process/stdout_stderr
|
||
/tool_process/nonzero_exit
|
||
/tool_process/missing_executable
|
||
/tool_process/working_directory
|
||
/tool_process/cancelled_before_start
|
||
/tool_process/cancelled_during_run
|
||
/tool_process/empty_output
|
||
/tool_process/non_utf8_output
|
||
/tool_process/signaled
|
||
```
|
||
|
||
---
|
||
|
||
## Fixtures de test
|
||
|
||
Une fixture peut contenir :
|
||
|
||
```c
|
||
typedef struct
|
||
{
|
||
char *temporary_directory;
|
||
char *tool_path;
|
||
} ToolProcessFixture;
|
||
```
|
||
|
||
Le `setup` doit :
|
||
|
||
- créer un dossier temporaire ;
|
||
- préparer les chemins.
|
||
|
||
Le `teardown` doit :
|
||
|
||
- supprimer tous les scripts ;
|
||
- supprimer les fichiers produits par les tests ;
|
||
- supprimer le dossier ;
|
||
- libérer toutes les chaînes.
|
||
|
||
Chaque test doit rester isolé.
|
||
|
||
---
|
||
|
||
## Makefile
|
||
|
||
Ajouter :
|
||
|
||
```make
|
||
TEST_TOOL_PROCESS := tests/test_tool_process
|
||
```
|
||
|
||
Règle attendue :
|
||
|
||
```make
|
||
$(TEST_TOOL_PROCESS): \
|
||
tests/test_tool_process.c \
|
||
src/core/tool_process.c
|
||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||
```
|
||
|
||
Ajouter la cible dans :
|
||
|
||
```make
|
||
test
|
||
```
|
||
|
||
et dans :
|
||
|
||
```make
|
||
clean
|
||
```
|
||
|
||
`TEST_LDFLAGS` contient déjà `gio-2.0`, nécessaire pour `GSubprocess`.
|
||
|
||
---
|
||
|
||
## Vérifications manuelles
|
||
|
||
```bash
|
||
make clean
|
||
make
|
||
make tests/test_tool_process
|
||
./tests/test_tool_process
|
||
make test
|
||
```
|
||
|
||
Aucun avertissement de compilation ne doit être accepté.
|
||
|
||
---
|
||
|
||
## Vérification mémoire
|
||
|
||
Exécuter :
|
||
|
||
```bash
|
||
G_DEBUG=gc-friendly \
|
||
G_SLICE=always-malloc \
|
||
valgrind \
|
||
--leak-check=full \
|
||
--show-leak-kinds=all \
|
||
./tests/test_tool_process
|
||
```
|
||
|
||
Vérifier particulièrement les chemins :
|
||
|
||
- lancement impossible ;
|
||
- code de sortie non nul ;
|
||
- annulation ;
|
||
- sortie vide ;
|
||
- erreur de communication.
|
||
|
||
---
|
||
|
||
## Vérification des processus enfants
|
||
|
||
Pendant le test d’annulation, contrôler si nécessaire qu’aucun faux outil ne reste actif après la fin du test.
|
||
|
||
Le module ne doit créer ni zombie ni processus abandonné.
|
||
|
||
---
|
||
|
||
## Critères d’acceptation
|
||
|
||
Le ticket est validé lorsque :
|
||
|
||
- le module compile en C17 strict ;
|
||
- il n’a aucune dépendance GTK ;
|
||
- il utilise `GSubprocess` ou `GSubprocessLauncher` ;
|
||
- il n’utilise aucun shell ;
|
||
- l’exécutable et les arguments sont transmis séparément ;
|
||
- `stdout` et `stderr` sont capturés séparément ;
|
||
- les sorties sont conservées dans des `GBytes` ;
|
||
- un code de sortie non nul produit tout de même un résultat ;
|
||
- l’échec de lancement produit une erreur ;
|
||
- le dossier de travail facultatif fonctionne ;
|
||
- l’annulation avant le lancement fonctionne ;
|
||
- l’annulation pendant l’exécution arrête le processus ;
|
||
- aucune sortie partielle n’est retournée après annulation ;
|
||
- les arguments contenant des caractères de shell restent littéraux ;
|
||
- les octets non UTF-8 sont conservés ;
|
||
- la terminaison par signal est représentée ;
|
||
- aucun processus enfant ne reste actif ;
|
||
- aucune fuite mémoire liée au module n’est détectée ;
|
||
- tous les tests passent ;
|
||
- le test est intégré à `make test` et `make clean`.
|
||
|
||
---
|
||
|
||
## Exemple d’utilisation attendu
|
||
|
||
```c
|
||
ToolProcessResult *result = NULL;
|
||
GBytes *stdout_bytes = NULL;
|
||
GError *error = NULL;
|
||
|
||
const char *arguments[] =
|
||
{
|
||
"+short",
|
||
"example.org",
|
||
NULL
|
||
};
|
||
|
||
if (!tool_process_run(
|
||
"/usr/bin/dig",
|
||
arguments,
|
||
NULL,
|
||
NULL,
|
||
&result,
|
||
&error
|
||
))
|
||
{
|
||
g_warning(
|
||
"Impossible d’exécuter dig : %s",
|
||
error != NULL
|
||
? error->message
|
||
: "erreur inconnue"
|
||
);
|
||
|
||
g_clear_error(
|
||
&error
|
||
);
|
||
|
||
return;
|
||
}
|
||
|
||
stdout_bytes = tool_process_result_ref_stdout(
|
||
result
|
||
);
|
||
|
||
if (!tool_process_result_is_success(
|
||
result
|
||
))
|
||
{
|
||
g_warning(
|
||
"dig a retourné le code %d",
|
||
tool_process_result_get_exit_status(
|
||
result
|
||
)
|
||
);
|
||
}
|
||
|
||
g_bytes_unref(
|
||
stdout_bytes
|
||
);
|
||
|
||
tool_process_result_free(
|
||
result
|
||
);
|
||
```
|
||
|
||
L’exemple illustre le comportement attendu. Il ne constitue pas une obligation d’organisation interne.
|
||
|
||
---
|
||
|
||
## Intégration future
|
||
|
||
Après validation de ce ticket :
|
||
|
||
1. création d’une tâche `BackgroundTask` exécutant `ToolProcess` ;
|
||
2. interrogation de la version des outils enregistrés ;
|
||
3. premier adaptateur OSINT, probablement DNS ;
|
||
4. conservation de la commande structurée :
|
||
- exécutable ;
|
||
- arguments ;
|
||
- date ;
|
||
- code de sortie ;
|
||
- sortie brute ;
|
||
- erreur brute ;
|
||
5. enregistrement de la provenance ;
|
||
6. affichage des résultats dans le panneau de travail ;
|
||
7. gestion ultérieure d’une durée maximale ;
|
||
8. ajout des dépendances facultatives aux installateurs Ubuntu.
|
||
|