ProtoFlix

Documentation · Comptes et sécurité

La conversion confinée

Comment ffmpeg est lancé, borné et isolé face à des fichiers hostiles.

Statut : isolation du moteur faite, voir ADR 0028 (§7). Le reste de ce document date de la conception et n'a pas été repris ligne à ligne. Les mesures de type seccomp/AppArmor sont à évaluer : elles ne seront annoncées qu'une fois testées sur la distribution de référence.

Décision associée : ADR 0010 (FFmpeg/ffprobe en processus enfant derrière une interface Engine).

1. Hypothèse de départ

Tout fichier média est une entrée hostile. FFmpeg et ffprobe parsent des centaines de formats ; des failles de démuxeurs et décodeurs sont publiées régulièrement. L'objectif n'est pas de rendre FFmpeg sûr, mais de limiter ce qu'un processus FFmpeg compromis ou emballé peut faire.

2. Construction des commandes

  • Lancement par exec.CommandContext avec arguments séparés. Jamais de sh -c, jamais de concaténation de chaîne.
  • Les arguments sont produits par l'adaptateur FFmpeg à partir d'un plan typé (codec, débit, résolution, pistes, filtres choisis dans des énumérations). Aucune valeur libre venant d'un client n'atteint la ligne de commande.
  • Les chemins d'entrée viennent exclusivement de la couche storage (§4), jamais d'une requête.
  • Les sorties sont écrites dans le répertoire de travail du job (§5), avec des noms générés par le serveur.
  • Les noms de fichiers d'entrée commençant par - ou contenant : sont passés sous une forme non ambiguë (chemin absolu, préfixe file:) pour éviter leur interprétation comme option ou protocole.
  • Option -nostdin, -hide_banner, sortie de progression lisible par machine (-progress pipe:1), stderr lu en continu sans blocage et tronqué au-delà d'une taille plafond.

3. Protocoles et démuxeurs

  • Entrées locales : -protocol_whitelist file (ajouter pipe uniquement si un flux interne l'exige). Interdit de facto http, https, tcp, udp, rtmp, concat, subfile, data, etc.
  • Les formats capables de référencer d'autres fichiers ou URL (playlists HLS/DASH en entrée, concat, ffconcat, références externes de certains conteneurs) sont refusés en entrée de bibliothèque ou traités par une politique explicite. Liste exacte à établir en phase 2 avec le corpus de test.
  • ffprobe reçoit les mêmes restrictions que ffmpeg. Fait (lot 2c, internal/mediaanalysis/ffprobe) : sous Linux et macOS, le serveur ouvre le fichier par la racine confinée de la source et le passe à ffprobe comme descripteur 3 (-protocol_whitelist fd -fd 3 -i fd:). ffprobe ne voit aucun chemin et ne peut rien ouvrir d'autre : ni file:, ni réseau, ni liste concat, ni lien hors source (vérifié par un test avec un vrai lien vers l'extérieur). Sous Windows, où Go ne transmet pas de descripteur supplémentaire, un chemin est passé après vérification que sa cible réelle reste dans la source, avec -protocol_whitelist file. Avant FFmpeg 6.0 (Debian 12 a la 5.1), le protocole fd n'existe pas : le moteur rouvre le descripteur par file:/dev/fd/3, avec -format_whitelist limité aux formats de fichiers multimédias. Les démultiplexeurs qui ouvrent d'autres fichiers (hls, dash, concat, vobsub…) en sont exclus, et une liste de lecture piégée est refusée (essayé). Sous Landlock, le lanceur ne permet de relire que ce fichier (essayé sur un noyau 6.18). Délai de 60 s par fichier, sorties bornées (16 Mio sur stdout, 8 Kio sur stderr), arguments jamais passés par un shell.
  • Les futures sources TV/IPTV, qui exigent des protocoles réseau, utiliseront un profil d'exécution séparé et explicitement autorisé, jamais le profil des bibliothèques.

4. Chemins validés par la couche storage

  • Chaque source de bibliothèque déclare une racine autorisée. Seule la couche storage résout un identifiant de fichier en chemin.
  • Normalisation, refus des composants .., refus de tout chemin résolu hors racine.
  • Liens symboliques : suivis uniquement si leur cible reste dans une racine autorisée ; sinon le fichier est ignoré et signalé dans les diagnostics admin.
  • os.Root : adopté (lot 2a, internal/storage). Toute lecture d'un fichier de bibliothèque passe par une racine confinée : .., chemins absolus et liens symboliques qui sortent de la source sont refusés par l'interface du système. C'est testé sous Linux avec un vrai lien vers l'extérieur. Les liens internes à la source restent suivis. Les racines elles-mêmes sont validées : chemin absolu, hors zones système (/etc, /proc, /sys, /dev, /var/lib… ; %SystemRoot% sous Windows), sans chevauchement avec DATA_DIR (base et clé) ni avec une autre source.
  • Les montages de médias sont en lecture seule (recommandé côté OS et imposé dans les exemples systemd/Docker).

5. Répertoires de travail

  • Un répertoire unique par job : <PROTOFLIX_TRANSCODE_DIR>/<job-id>/, créé en 0700, propriété de l'utilisateur de service.
  • PROTOFLIX_TRANSCODE_DIR doit être sur un volume distinct ou soumis à quota (§6) ; jamais dans le répertoire de la base ni dans une bibliothèque.
  • Suppression à la fin du job, à l'annulation, et par un nettoyage au démarrage (répertoires orphelins après crash) et périodique (TTL).
  • Le serveur sert les segments en lisant ce répertoire via des noms qu'il a générés, jamais via un nom fourni par le client.

6. Limites de ressources

RessourceMécanisme prévu (Linux)
Duréecontext avec échéance par job ; watchdog qui tue les processus sans progression pendant N secondes
Mémoirecgroup v2 : MemoryMax= sur l'unité systemd du service (ou d'un sous-scope dédié aux transcodages) ; Docker : mem_limit
CPUCPUQuota= / CPUWeight= ; priorité nice plus basse pour les jobs de fond que pour la lecture interactive
ProcessusTasksMax= ; FFmpeg -threads borné par le Resource Manager
Fichiers et taille de sortieLimitNOFILE= ; quota disque du répertoire de transcodage et plafond par job
Entrées excessivesRejet avant lancement : résolution, nombre de pistes, durée, débit au-delà de plafonds configurables (valeurs issues de l'analyse ffprobe)
ConcurrenceResource Manager (ADR / brief §7.4) : nombre de transcodages simultanés configurable, rejet ou dégradation contrôlée plutôt que saturation

L'option la plus robuste étudiée : lancer chaque transcodage dans un scope systemd transitoire (systemd-run --scope ou API D-Bus) avec ses propres limites. Coût de complexité et disponibilité hors systemd (Docker) à évaluer ; à défaut, limites globales du service + setrlimit sur le processus enfant.

7. Compte et privilèges

  • Le service tourne sous un utilisateur système dédié protoflix, jamais root.
  • FFmpeg hérite de cet utilisateur. Option étudiée : un second utilisateur protoflix-media encore plus restreint pour les processus enfants (lecture seule des médias, écriture du seul répertoire de transcodage), au prix d'une gestion des permissions plus complexe.
  • Accès GPU : appartenance aux groupes render/video uniquement si l'accélération est activée.
  • NoNewPrivileges=yes, PrivateTmp=yes, ProtectSystem=strict, ProtectHome=yes, ReadOnlyPaths= pour les bibliothèques, ReadWritePaths= limité aux répertoires de données (voir docs/operations/deployment.md). Compatibilité de chaque option avec l'accès GPU à tester.

8. Seccomp, AppArmor, isolation supplémentaire

À évaluer en phase 6, sans engagement :

  • SystemCallFilter=@system-service sur l'unité systemd, avec liste d'exceptions mesurée (les pilotes GPU utilisent des ioctl spécifiques).
  • Profil AppArmor (Debian/Ubuntu) pour FFmpeg : lecture des racines de bibliothèques, écriture du répertoire de transcodage, accès /dev/dri ou /dev/nvidia*.
  • Isolation réseau des processus FFmpeg (PrivateNetwork= sur un scope dédié, ou espace de noms réseau) : une défense forte contre l'exfiltration, à condition que l'IPTV utilise un profil séparé.

Critère d'adoption : la mesure ne casse ni l'accélération matérielle validée ni les performances au-delà d'un seuil mesuré.

9. Fichiers hostiles : comportement attendu

  • Échec d'analyse ou de transcodage ⇒ erreur classée (file_corrupt, unsupported_format, resource_limit, engine_crash), visible dans les diagnostics admin, sans chemin ni sortie brute pour les non-admins.
  • Un fichier qui fait planter ffprobe de façon répétée est mis en quarantaine logique (plus de nouvelles tentatives automatiques) jusqu'à action admin ou changement du fichier/de la version de FFmpeg.
    • État au lot 2c : un seul échec suffit. Le fichier est enregistré unreadable avec le message de ffprobe, et n'est plus analysé tant que son contenu ne change pas ou que la version du normaliseur (ParserVersion) n'augmente pas. Une nouvelle version de FFmpeg ne relance pas encore ces fichiers ; l'action admin non plus (à faire avec la vue des diagnostics).
  • Pas de boucle de retries infinie : nombre de tentatives borné par le système de jobs.

10. Maintenance

  • Version et configuration de compilation de FFmpeg enregistrées et affichées dans les diagnostics.
  • Suivi des avis de sécurité FFmpeg ; mise à jour du binaire distribué dans un délai défini par la politique de publication (à fixer en phase 6).
  • Licences et options --enable-gpl / --enable-nonfree inventoriées (SBOM), voir docs/media/.

Source : docs/security/transcoder-hardening.md, dans le dépôt de ProtoFlix.