
Analisi tecnica della vulnerabilità e proof-of-concept per CVE-2026-47630, un dlopen arbitrario tramite TRITON_BATCH_STRATEGY_PATH in NVIDIA Triton Inference Server che consente l'esecuzione di codice nativo, con indicazioni per il rilevamento e la mitigazione.
dlopen arbitrario tramite TRITON_BATCH_STRATEGY_PATHTraversal assoluto del percorso nel caricatore personalizzato della strategia di batching di
triton-inference-server/core. Un parametro di configurazione del modello viene passato
senza validazione a dlopen(RTLD_NOW | RTLD_LOCAL), quindi chiunque sia in grado di influenzare il
config.pbtxt di un modello ottiene l'esecuzione di codice nativo all'interno del processo del server Triton
al momento del caricamento del modello.
| CVE | CVE-2026-47630 |
| Bollettino NVIDIA | 5865 (2026-08-18) |
| CWE | CWE-36 (Absolute Path Traversal); segnalata come CWE-114 / CWE-829 |
| CVSS v3.1 (NVIDIA) | 5.5 MEDIO — AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Versioni interessate | Triton Inference Server 0.0 – 26.05 (Linux) |
| Corretta in | 26.06 |
| Segnalata da | s1ko (github.com/s1ko, [email protected]) |
| Tracciamento del fornitore | NVIDIA PSIRT ticket 6139742 |
La dichiarazione di NVIDIA nel bollettino 5865 recita testualmente: CVE-2026-47630: s1ko.
Triton accetta un parametro per modello TRITON_BATCH_STRATEGY_PATH dal
config.pbtxt del modello. Fino alla 26.05 il valore veniva trattato come un percorso di filesystem arbitrario
e passato senza modifiche a dlopen. Non c'era alcun controllo di contenimento,
nessuna allowlist e nessuna verifica di firma o hash. Percorsi assoluti,
sequenze di traversal e symlink erano tutti accettati.
RTLD_NOW risolve immediatamente ogni simbolo ed esegue le routine
__attribute__((constructor)) / .init_array dell'oggetto prima che dlopen
restituisca il controllo. L'esecuzione è quindi incondizionata rispetto al successo del caricamento — nessuno
dei punti di ingresso TRITONBACKEND_ModelBatch* deve esistere perché il payload venga
eseguito, e viene eseguito con i privilegi del processo Triton (spesso root
nei container NGC).
src/backend_model.cc — l'input dell'attaccante raggiunge batch_libpath, validato solo per l'esistenza:
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());
}
}
Degno di nota è l'assenza di qualsiasi chiamata a IsChildPathEscapingParentPath,
che Triton applicava già ai percorsi dei label e ai percorsi delle librerie backend altrove nello stesso file.
src/backend_model.cc — SetBatchingStrategy lo inoltra:
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 — il sink:
*handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
Tre vie pratiche per controllare model_config.parameters():
config.pbtxt del proprio modello
piazza un .so nella propria directory e punta il parametro a quel file. Il payload viene
eseguito con i privilegi del server, dando accesso ai pesi, ai segreti e alla GPU di ogni altro tenant.TRITONSERVER_ServerLoadModelWithParameters combinata con il prefisso documentato
file:<rel-path> per i parametri consente a un chiamante con i diritti di caricamento dei modelli di inviare
config.pbtxt e il .so in una singola richiesta e di attivare il caricamento immediatamente.
Non è richiesto alcun accesso al filesystem al di fuori dell'API.dlopen on demand.poc/ contiene la primitiva, ridotta a ciò che fa OpenLibraryHandle. Solo sorgenti — compilali tu stesso:
$ 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
Il payload è inerte: aggiunge una riga che registra pid, uid, euid e cwd. Verificato su Debian 13, GCC 14.2, x86_64.
Per l'innesco lato Triton, posiziona l'evil.so compilato accanto a
poc/config.pbtxt nel repository dei modelli e carica il
modello — auto-load, --load-model, oppure
POST /v2/repository/models/evil_model/load. Su una versione affetta il
costruttore scatta durante SetBatchingStrategy, prima di qualsiasi ricerca di
simboli del backend.
Il caricamento viene registrato da Triton stesso a livello INFO:
Loading custom batching strategy library <path> for model <name>
Su una build corretta, un tentativo rifiutato compare come
Batching library path escapes model repository.
Qualsiasi valore di TRITON_BATCH_STRATEGY_PATH che sia assoluto, contenga .., o
risolva all'esterno della directory del modello merita un alert indipendentemente
dalla versione. Una regola Sigma che copre sia la riga di log sia il parametro di
configurazione si trova in detection/.
Segnali complementari:
config.pbtxt che contengono un parametro TRITON_BATCH_STRATEGY_PATH —
la funzionalità è rara nella pratica, quindi la semplice presenza è già un filtro utile.openat/mmap di un .so al di fuori della root del modello da parte del
processo tritonserver (auditd, eBPF o Falco).Aggiorna a Triton Inference Server 26.06 o successivo. La correzione aggiunge il controllo di contenimento che mancava al caricatore:
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.");
}
Dove l'aggiornamento non è immediatamente possibile, controlli compensativi:
TRITON_BATCH_STRATEGY_PATH da ogni config.pbtxt
prima che raggiunga il repository dei modelli.--model-control-mode diverso da
explicit, oppure autorizzazione sugli endpoint del repository) a meno che non sia richiesto.tritonserver come utente senza privilegi con un mount di sola lettura
del repository dei modelli, così che un caricamento riuscito abbia il raggio
di esplosione più piccolo possibile.Mappatura: MITRE ATT&CK T1574.006 Hijack Execution Flow: Dynamic Linker
Hijacking e
T1129 Shared Modules;
NIST SP 800-53r5 SI-7, CM-5, SC-18; CIS Controls v8 §2, §4.
| Date | Event |
|---|---|
| 2026-04-25 | Segnalata la scoperta primaria relativa alla gestione dei percorsi in Triton; NVIDIA PSIRT apre il ticket 6128799 |
| 2026-05-01 | Questa scoperta secondaria è stata confermata e segnalata; PSIRT apre il ticket 6139742 |
| 2026-05-28 | Concordata con PSIRT la cadenza per la divulgazione coordinata |
| 2026-06 | La correzione viene distribuita con Triton Inference Server 26.06 |
| 2026-08-18 | NVIDIA pubblica il bollettino 5865, assegnando CVE-2026-47630 e attribuendo il merito a s1ko |
| 2026-08-22 | Pubblicato questo write-up |
triton-inference-server/core — https://github.com/triton-inference-server/coreMIT — vedi LICENSE.