
Analyse technique de vulnérabilité et preuve de concept pour CVE-2026-47630, un dlopen arbitraire via TRITON_BATCH_STRATEGY_PATH dans NVIDIA Triton Inference Server permettant l'exécution de code natif, avec des conseils de détection et d'atténuation.
dlopen arbitraire via TRITON_BATCH_STRATEGY_PATHTraversée de chemin absolu dans le chargeur personnalisé de stratégie de batching de
triton-inference-server/core. Un paramètre de configuration de modèle est transmis
sans validation à dlopen(RTLD_NOW | RTLD_LOCAL), de sorte que toute personne capable
d'influencer le config.pbtxt d'un modèle obtient une exécution de code natif dans le
processus du serveur Triton au moment du chargement du modèle.
L'accusé de réception de NVIDIA dans le bulletin 5865 se lit textuellement : CVE-2026-47630: s1ko.
Triton accepte un paramètre TRITON_BATCH_STRATEGY_PATH par modèle, issu du
config.pbtxt du modèle. Jusqu'à la version 26.05, la valeur était traitée comme un
chemin de système de fichiers arbitraire et transmise sans modification à dlopen.
Aucune vérification de confinement, aucune liste blanche, ni vérification de signature
ou de hachage. Les chemins absolus, les séquences de traversée et les liens symboliques
étaient tous acceptés.
RTLD_NOW résout chaque symbole immédiatement et exécute les routines
__attribute__((constructor)) / .init_array de l'objet avant que dlopen ne
retourne. L'exécution est donc inconditionnelle à un chargement réussi — aucun des
points d'entrée TRITONBACKEND_ModelBatch* n'a besoin d'exister pour que la charge
utile s'exécute, et elle s'exécute avec les privilèges du processus Triton (souvent
root dans les conteneurs NGC).
src/backend_model.cc — l'entrée de l'attaquant atteint batch_libpath, validée
uniquement pour son existence :
if (model_config.parameters().contains("TRITON_BATCH_STRATEGY_PATH")) {
batch_libpath = model_config.parameters()
.at("TRITON_BATCH_STRATEGY_PATH")
.string_value();
bool exists = false;
RETURN_IF_ERROR(FileExists(batch_libpath, &exists));
if (!exists) {
return Status(
triton::common::Error::Code::NOT_FOUND,
("Batching library path not found: " + batch_libpath).c_str());
}
}
On note l'absence de tout appel à IsChildPathEscapingParentPath, que Triton
appliquait déjà aux chemins de labels et aux chemins de bibliothèques backend ailleurs
dans le même fichier.
src/backend_model.cc — SetBatchingStrategy le transmet :
Status TritonModel::SetBatchingStrategy(const std::string& batch_libpath)
{
std::unique_ptr<SharedLibrary> slib;
RETURN_IF_ERROR(SharedLibrary::Acquire(&slib));
RETURN_IF_ERROR(slib->OpenLibraryHandle(batch_libpath, &batch_dlhandle_));
src/shared_library.cc — le sink :
*handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
Trois voies pratiques pour contrôler model_config.parameters() :
config.pbtxt
de son modèle place un .so dans son propre répertoire et y fait pointer le
paramètre. La charge utile s'exécute avec les privilèges du serveur, ce qui donne
accès aux poids, aux secrets et au GPU de tous les autres locataires.TRITONSERVER_ServerLoadModelWithParameters,
combinée au préfixe de paramètre documenté file:<rel-path>, permet à un appelant
disposant des droits de chargement de modèles de soumettre config.pbtxt et le
.so dans une seule requête et de déclencher immédiatement le chargement. Aucun
accès au système de fichiers en dehors de l'API n'est requis.dlopen
à la demande.poc/ contient la primitive, réduite à ce que fait OpenLibraryHandle.
Sources uniquement — compilez-les vous-même :
$ cd poc && ./build.sh
$ gcc -O0 -o test_dlopen test_dlopen.c -ldl
$ ./test_dlopen ./evil.so
dlopen OK, handle=0x55d1779652c0
$ cat /tmp/triton_dlopen_rce_proof.log
[triton-dlopen-rce] constructor fired @ Fri May 1 09:56:56 2026
pid=240157 uid=1000 euid=1000 cwd=/opt/poc
La charge utile est inerte : elle ajoute une ligne enregistrant pid, uid, euid et cwd. Vérifiée sur Debian 13, GCC 14.2, x86_64.
Pour le déclencheur côté Triton, placez le evil.so compilé à côté de
poc/config.pbtxt dans le dépôt de modèles et chargez le modèle —
chargement automatique, --load-model, ou
POST /v2/repository/models/evil_model/load. Sur une version affectée, le constructeur
se déclenche pendant SetBatchingStrategy, avant toute recherche de symbole backend.
Le chargement est journalisé par Triton lui-même au niveau INFO :
Loading custom batching strategy library <path> for model <name>
Sur une version corrigée, une tentative rejetée se manifeste par
Batching library path escapes model repository.
Toute valeur TRITON_BATCH_STRATEGY_PATH qui est absolue, contient .., ou se résout
en dehors du répertoire du modèle mérite une alerte, quelle que soit la version. Une
règle Sigma couvrant à la fois la ligne de journal et le paramètre de configuration se
trouve dans detection/.
Signaux complémentaires :
config.pbtxt comportant un paramètre TRITON_BATCH_STRATEGY_PATH —
la fonctionnalité est rare en pratique, si bien que sa seule présence constitue un
filtre utile.openat/mmap d'un .so en dehors de la racine du modèle par le processus
tritonserver (auditd, eBPF ou Falco).Mettez à niveau vers Triton Inference Server 26.06 ou version ultérieure. Le correctif ajoute la vérification de confinement qui manquait au chargeur :
bool escapes{true};
RETURN_IF_ERROR(IsChildPathEscapingParentPath(
batch_libpath, localized_model_dir->Path(), &escapes));
if (escapes) {
return Status(
Status::Code::INVALID_ARG,
"Batching library path escapes model repository.");
}
Lorsque la mise à niveau n'est pas immédiatement possible, contrôles compensateurs :
TRITON_BATCH_STRATEGY_PATH de chaque config.pbtxt avant
qu'il n'atteigne le dépôt de modèles.--model-control-mode
avec une valeur autre que explicit, ou autorisation sur les points de terminaison
du dépôt) sauf si elle est requise.tritonserver en tant qu'utilisateur non privilégié avec un montage en
lecture seule du dépôt de modèles, afin qu'un chargement réussi ait le plus petit
rayon d'impact possible.Correspondance : MITRE ATT&CK
T1574.006 Détournement du flux d'exécution : détournement de l'éditeur de liens dynamique
et T1129 Modules partagés ;
NIST SP 800-53r5 SI-7, CM-5, SC-18 ; CIS Controls v8 §2, §4.
triton-inference-server/core — https://github.com/triton-inference-server/coreMIT — voir LICENSE.
| CVE | CVE-2026-47630 |
| Bulletin NVIDIA | 5865 (2026-08-18) |
| CWE | CWE-36 (Traversée de chemin absolu) ; signalé comme CWE-114 / CWE-829 |
| CVSS v3.1 (NVIDIA) | 5.5 MOYEN — AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Versions affectées | Triton Inference Server 0.0 – 26.05 (Linux) |
| Corrigé dans | 26.06 |
| Signalé par | s1ko (github.com/s1ko, [email protected]) |
| Suivi fournisseur | Ticket NVIDIA PSIRT 6139742 |
| Date | Événement |
|---|
| 2026-04-25 | Découverte principale relative à la gestion des chemins de Triton signalée ; NVIDIA PSIRT ouvre le ticket 6128799 |
| 2026-05-01 | Cette découverte secondaire confirmée et signalée ; PSIRT ouvre le ticket 6139742 |
| 2026-05-28 | Cadence de divulgation coordonnée convenue avec le PSIRT |
| 2026-06 | Le correctif est livré dans Triton Inference Server 26.06 |
| 2026-08-18 | NVIDIA publie le bulletin 5865, attribuant CVE-2026-47630 et créditant s1ko |
| 2026-08-22 | Publication de cette analyse |