SlideShare a Scribd company logo
1 of 8
Download to read offline
UNIVERSITÀ DEGLI STUDI DI TRIESTE
DIPARTIMENTO DI INGEGNERIA E ARCHITETTURA
Corso di Studi in Ingegneria Elettronica e Informatica
Summary of “The Case for Writing Network Drivers in
High-Level Programming Languages" [1]
Tesi di Laurea Triennale
Laureando:
Leonardo IURADA
Relatore:
prof. Marco TESSAROTTO
ANNO ACCADEMICO 2019/2020
Indice
1 Introduzione 1
2 Metodo d’indagine 2
2.1 Architettura del Device Driver . . . . . . . . . . . . . . . . . . . 2
2.2 Implementazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.3 Test Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
3 Risultati 4
3.1 Prestazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.2 Il costo delle funzionalità di sicurezza . . . . . . . . . . . . . . . . 4
3.3 Latenza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4 Conclusione 6
1 Introduzione
C è stato il linguaggio di programmazione prediletto per scrivere i kernel sin dal
suo concepimento. Anche la maggior parte dei driver dei dispositivi sono scritti
in C, o con un sottoinsieme ristretto di C++ fornendo praticamente nessuna
funzionalità di sicurezza aggiuntiva. Sono stati trovati 65 bug di sicurezza in
Linux nel 2017 di cui: 8 sono bug use-after-free o bug double-free, 32 sono bug
che permettono di eseguire accessi out-of-bounds, 14 sono bug logici e 11 sono
bug il cui effetto rimane ancora poco chiaro. I driver rappresentano il 66% del
codice in Linux, ma 39 su 40 di questi bug di sicurezza relativi alla memory
safety sono localizzati nei driver e possono essere mitigati usando un linguaggio
di programmazione che imponga dei controlli di sicurezza.
Questo studio si propone di valutare la possibilità di utilizzare linguaggi
di programmazione ad alto livello per scrivere driver a livello di spazio utente,
scegliendo come punto di partenza di studiare il comportamento di tali linguaggi
nei driver di rete. I linguaggi ad alto livello infatti sono più sicuri (meno bug,
più controlli di sicurezza), ma i safety checks al runtime riducono il throughput e
la garbage collection porta a picchi di latenza. Quindi, questo studio si propone
di far luce su due aspetti principali di questo problema: valutare quali linguaggi
siano adatti allo sviluppo di user space driver e confrontare costi e benefici dei
vari aspetti di sicurezza apportati da tali linguaggi.
La scelta di passare da driver kernel a user space driver è dettata dal fatto
che i driver sono anche le superfici d’attacco più grandi (in termini di righe di
codice) nei moderni sistemi operativi e continuano a crescere in complessità man
mano che vengono aggiunte ulteriori funzionalità. Gli user space driver inoltre
sono molto più isolati dal resto del sistema rispetto ai kernel driver: eseguire un
accesso out-of-bounds è ancora un bug in alcuni linguaggi memory-safe, ma fa
sì che il programma vada in crash se non gestito; declassando una vulnerabilità
riguardante l’esecuzione arbitraria di codice in un denial of service (DoS). Gli
user space driver possono semplicemente essere riavviati dopo un crash, mentre
un crash di un driver kernel di solito fa sì che l’intero sistema collassi.
La decisione di studiare il comportamento dei driver di rete non è casuale:
negli ultimi anni, i driver dei dispositivi di rete sono riusciti a sfuggire dal-
le grinfie del kernel grazie agli user space driver (esempio, DPDK, framework
per costruire applicativi in grado di processare pacchetti in maniera rapida,
costituito da diversi user space driver).
1
2 Metodo d’indagine
Per indagare la questione, centro di questa ricerca, è stato scelto di utilizzare
Linux x86 e sono state presentate implementazioni di uno user space driver
ottimizzato per le prestazioni per la famiglia di controller di rete “Intel ixgbe”
(82599ES, X540, X550, e X552) in 9 linguaggi ad alto livello (Rust, Go, C, Java,
OCaml, Haskell, Swift, JavaScript, e Python) le cui caratteristiche principali
sono riassunte nella Tabella 1. Tutte le implementazioni sono scritte da zero
da programmatori esperti nei rispettivi linguaggi di programmazione in stile
idiomatico e seguono la stessa architettura di base, permettendo un confronto
in termini di prestazioni tra i linguaggi ad alto livello e l’implementazione di
riferimento in C.
Linguaggio Paradigma principale∗
Memory mgmt. Compilazione
Rust Imperative Ownership/RAII†
Compilato‡
Go Imperative Garbage collection Compilato
C# Object-oriented Garbage collection JIT
Java Object-oriented Garbage collection JIT
OCaml Functional Garbage collection Compilato
Haskell Functional Garbage collection Compilato‡
Swift Protocol-oriented Reference counting Compilato‡
JavaScript Imperative Garbage collection JIT
Python Imperative Garbage collection Interpretato
∗
Tutti i linguaggi selezionati sono multi-paradigma
†
L’acquisizione delle risorse è l’Inizializzazione
‡
Usando LLVM
Tabella 1: Linguaggi usati nelle implementazioni
2.1 Architettura del Device Driver
L’architettura del driver ixy (la cui versione di riferimento è scritta in C) è
ispirata al DPDK, vale a dire, usa driver in modalità polling senza interrupts,
tutte la API si basano sul processare pacchetti in batch, e sfruttano l’UIO
subsystem. Nei linguaggi in cui le syscall mmap e mlock non sono disponibili,
vengono sfruttate piccole funzioni in C.
Inoltre, in ogni linguaggio sono stati pensati wrapper specifici per lo spazio
d’indirizzi MMIO PCIe e per i DMA buffer in modo da poter fornire un mecca-
nismo di boundary checking e permettere l’accesso alla memoria senza causare
violazioni. In ogni caso, sia l’uso di mmap che l’uso di struct costruiti a partire
da un puntatore e una lunghezza sono operazioni intrinsecamente non sicure.
L’obiettivo è quello di ridurre queste operazioni a meno aree di codice possibile
per ridurre la quantità di codice da monitorare manualmente per evitare errori
di memoria.
Ultima considerazione sull’architettura del driver: mentre in C è prevista
la keyword volatile per evitare che le ottimizzazioni introdotte dal compila-
tore vadano ad introdurre comportamenti non voluti nell’esecuzione del codice
(esempio, leggere ripetutamente lo stesso registro in un loop mentre si attende
che un valore venga modificato dal dispositivo sembra un’opportunità per l’ot-
timizzatore di sollevare la lettura dal loop), nei linguaggi ad alto livello possono
essere usate utility di gestione della concorrenza.
2
2.2 Implementazioni
Tutte le implementazioni nei linguaggi ad alto livello hanno richiesto più righe
di codice rispetto a C. La Tabella 2 riassume le protezioni contro le classi di
bug disponibili sia per le implementazioni dei driver che per le applicazioni
basate su di essi. Il vantaggio qui è che linguaggi ad alto livello non aumentano
necessariamente la difficoltà d’implementazione per il programmatore ottenendo
vantaggi in termini di sicurezza.
Memoria Generale Packet Buffer
Ling. OoB1
Use after free OoB1
Use after free Int overflows
C     
Rust   ()2
 ()5
Go   ()2
()4

C#   ()2
()4
()5
Java   ()2
()4

OCaml   ()2
()4

Haskell   ()2
()4
()6
Swift   3
()4

JavaScript   ()2
()4
()6
Python   ()2
()4
()6
1
Accesso Out of Bounds.
2
Bounds imposti da wrapper, constructor in codice unsafe o C.
3
Bounds imposti solo in debug mode.
4
Buffer mai liberati/garbage collected, solo ritornati ad una memory pool.
5
Disabilitato di default.
6
Di default usa floating point precision anche per gli integer.
Tabella 2: Protezioni a livello di linguaggio contro le classi di bug nei
driver.
L’unica implementazione non ottimizzata per alte prestazioni è quella in Py-
thon: essa è stata pensata come un semplice strumento educativo. Nella Tabella
3 riassumiamo i principali metodi di wrapping usati nelle implementazioni.
Linguaggio Gestione ed Accesso in Memoria Wrapper Memoria Esterna
Rust Ownership system std::slice objects
ptr module Rust’s struct
Go atomic package slice data type
C# Unsafe mode Marshal in System.Runtime.InteropServices
Java sun.misc.Unsafe Object Java Class
OCaml C helper functions OCaml’s Bigarrays
Cstruct library
Haskell System.Posix.Memory Foreign package
Swift Automatic Reference counting UnsafeBufferPointers
JavaScript Node.js module in C JS’ ArrayBuffers
Python Cython Python Class
Tabella 3: Metodi di wrapping in ogni implementazione
2.3 Test Setup
Per stimare le prestazioni del routing (misurate in Mpps), i driver vengono
eseguiti su una CPU Xeon E3-1230 v2 con clock a 3.3GHz con due 10Gbit/s
Intel X520 NIC. Il traffico di test è generato con MoonGen. I due NIC eseguono
3
un’applicazione di forwarding che modifica un byte nell’header del pacchetto.
Tutti i test sono ristretti all’uso di un singolo core della CPU.
Per valutare la latenza invece, viene eseguito il mirroring di tutti i pacchetti
per mezzo di uno splitter in fibra ottica su un server che esegue MoonSniff con
timestamp registrati a livello hardware su una NIC integrata Xeon D (precisione
misurata 24 ns, senza perdite di pacchetti). Il dispositivo sotto test usa un Intel
Xeon E5-2620 v3 a 2.40 GHz e un dual-port Intel X520 NIC. Tutte le latenze
sono state misurate con una dimensione batch di 32 sotto carico bidirezionale
con traffico a bit rate costante.
3 Risultati
3.1 Prestazioni
Processare pacchetti in batch è un concetto chiave in tutti i veloci driver di
rete. Ogni batch ricevuta o inviata richiede la sincronizzazione con la scheda di
rete, batch più grandi incrementano quindi le prestazioni. Batch troppo grandi
riempiono le cache della CPU quindi si hanno pochi miglioramenti o addirittura
una riduzione delle prestazioni. Da 32 a 128 pacchetti per batch è la scelta
ottima per gli user space driver. La Figura 1 mostra le prestazioni massime
raggiunte nel forwarding bidirezionale dalle varie implementazioni. Haskell e
OCaml allocano una nuova lista/array per ogni batch di pacchetti che viene
processata mentre tutti gli altri linguaggi riutilizzano gli array. Riciclare gli
array in questi linguaggi funzionali violerebbe la volontà di scrivere codice in
stile idiomatico, questa è una delle ragioni per le loro prestazioni più basse.
Figura 1: Forwarding Rate on 3.3 GHz CPU.
3.2 Il costo delle funzionalità di sicurezza
Rust è l’implementazione più rapida che raggiunge oltre il 90% delle prestazioni
di C. È anche l’unico linguaggio di alto livello senza costi generali per la gestione
della memoria, che lo rende un candidato ideale per indagini ulteriori.
4
Eventi per pacchetto C Rust
Cicli 94 100
Istruzioni 127 209
Istr. per ciclo 1.35 2.09
Branches 18 24
Branches mispredicts 0.05 0.08
Store µops 21.8 37.4
Load µops 30.1 77.0
Load L1 hits 24.3 75.9
Load L2 hits 1.1 0.05
Load L3 hits 0.9 0.0
Load L3 misses 0.3 0.1
Tabella 4: Prestazioni in eventi per
pacchetto. Batch 32, 1.6 GHz.
Lo svantaggio di performance
principale è dato dal boundary chec-
king. La Tabella 4 elenca i risulta-
ti in eventi per pacchetto inoltrato.
Rust richiede il 65% di istruzioni in
più per inoltrare un singolo pacchet-
to a una dimensione di batch di 32.
Il numero di branch eseguite aumen-
ta del 33%, il numero di load anche
del 150%. Tuttavia, il codice Rust ri-
chiede solo il 6% di cicli in più per
pacchetto nel complesso nonostante
faccia più lavoro. Una moderna su-
perscalar CPU con esecuzione out-of-
order può effettivamente nascondere
l’overhead introdotto dai controlli di
sicurezza: il processore è in grado di
prevedere correttamente (il tasso di errore del branch è allo 0,2% - 0,3%) ed ese-
guire speculativamente il percorso corretto. Un’altra caratteristica di sicurezza
di Rust sono i controlli sugli integer overflow: devono tuttavia essere attiva-
ti esplicitamente con una flag al momento della compilazione. Facendo così si
decrementa il throughput di solo lo 0.8% con batch di dimensione 8, nessuna
deviazione statistica significativa è stata misurata con batch di dimensione più
grande. Anche in questo caso la speculative execution nascondendo il costo delle
caratteristiche di sicurezza.
3.3 Latenza
La latenza è dominata dal tempo impiegato nel buffer, non dal tempo impiegato
nella gestione di un pacchetto dalla CPU. I driver inoltrano i pacchetti in centi-
naia di cicli, ovvero entro centinaia di nanosecondi. Pertanto, un driver con un
throughput inferiore non è automaticamente uno con una latenza più elevata
durante il funzionamento al limite massimo del carico di lavoro. I principali fat-
tori che determinano l’aumento della latenza sono le pause dovute alla garbage
collection e alle dimensioni della batch. I driver funzionano con un buffer di
dimensione 512 di default e configurano la NIC per eliminare i pacchetti se il
buffer di ricezione è pieno.
La Figura 2 mostra le latenze di coda dei driver durante l’inoltro di pacchetti
a velocità diverse. I dati vengono tracciati come CCDF per concentrarsi sulle
latenze nel caso peggiore.
5
Figura 2: Latenze di coda durante l’inoltro dei pacchetti.
La loro latenza è semplicemente una funzione della dimensione del buffer di
ricezione man mano che esso si riempie completamente. Nessuna latenza mas-
sima osservata è significativamente diversa dal 99.9999-esimo percentile. Java e
JavaScript perdono i pacchetti durante l’avvio a causa della compilazione JIT,
pertanto escludiamo i primi 5 secondi dell’esecuzione del test per questi due
linguaggi. Tutti i test mostrati sono stati eseguiti senza perdita di pacchetti.
4 Conclusione
Riscrivere l’intero sistema operativo in un linguaggio di alto livello è uno sforzo
lodevole ma difficilmente rimpiazzerà l’utilizzo dei sistemi operativi desktop e
server tradizionali nel prossimo futuro. Si propone di iniziare a riscrivere i driver
come user space driver in linguaggi di alto livello poiché 39 dei 40 bug di sicurezza
della memoria di Linux elencati si trovano nei driver, a dimostrazione del fatto
che la maggior parte dei miglioramenti in termini di sicurezza possono essere
ottenuti senza sostituire l’intero sistema operativo. I driver di rete sono un buon
punto di partenza per questo sforzo: i driver di rete a livello di spazio utente
scritti in C sono già all’ordine del giorno (esempio, DPDK). Dai test eseguiti
emerge che Rust è il primo candidato come linguaggio per driver più sicuri:
il suo ownership system previene bug di memoria anche in aree di memoria
personalizzate non allocate dal runtime del linguaggio. Il costo di questi aspetti
di sicurezza sono solo il 2% - 10% del throughput su moderne superscalar CPU
out-of-order. L’ownership system di Rust fornisce più funzionalità di sicurezza
rispetto ai linguaggi basati sulla garbage collection senza influenzare la latenza.
Anche Go e C# sono dei linguaggi adatti se il sistema è in grado di far fronte a
picchi di latenza inferiori al millisecondo dovuti alla garbage collection. Gli altri
linguaggi discussi qui possono anche essere utili se le prestazioni sono meno
critiche rispetto ad avere un sistema sicuro e corretto, ad esempio Haskell e
OCaml sono più adatti alla verifica formale.
Riferimenti bibliografici
[1] Paul Emmerich, Simon Ellmann, Fabian Bonk, Alex Egger, Esaú García Sánchez-
Torija, Thomas Günzel, Sebastian Di Luzio, Alexandru Obada, Maximilian Stadl-
meier, Sebastian Voit, Georg Carle The Case for Writing Network Drivers in
High-Level Programming Languages ANCS’ 19, 13 September 2019
6

More Related Content

Similar to Summary of “The Case for Writing Network Drivers in High-Level Programming Languages"

Summary of “The Case for Writing Network Drivers in High-Level Programming La...
Summary of “The Case for Writing Network Drivers in High-Level Programming La...Summary of “The Case for Writing Network Drivers in High-Level Programming La...
Summary of “The Case for Writing Network Drivers in High-Level Programming La...LeonardoIurada
 
Progettazione e sviluppo di un software applicativo su un single board computer
Progettazione e sviluppo di un software applicativo su un single board computerProgettazione e sviluppo di un software applicativo su un single board computer
Progettazione e sviluppo di un software applicativo su un single board computerAlessandro Mascherin
 
Webinar porting e ottimizzazione per x86
Webinar   porting e ottimizzazione per x86Webinar   porting e ottimizzazione per x86
Webinar porting e ottimizzazione per x86Massimiliano Torregiani
 
J2Me Il Micro Mondo Java
J2Me Il Micro Mondo JavaJ2Me Il Micro Mondo Java
J2Me Il Micro Mondo JavaAntonio Terreno
 
PIT2012: Workshop@UniNA - Compilazione del Kernel Linux
PIT2012: Workshop@UniNA - Compilazione del Kernel LinuxPIT2012: Workshop@UniNA - Compilazione del Kernel Linux
PIT2012: Workshop@UniNA - Compilazione del Kernel LinuxMarco Ferrigno
 
Trace32 lo-strumento-piu-completo-per-il-debug-di-un-sistema-linux
Trace32 lo-strumento-piu-completo-per-il-debug-di-un-sistema-linuxTrace32 lo-strumento-piu-completo-per-il-debug-di-un-sistema-linux
Trace32 lo-strumento-piu-completo-per-il-debug-di-un-sistema-linuxDeveler S.r.l.
 
Generazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGenerazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGiacomoZorzin
 
MITM Attack with Patching Binaries on the Fly by Adding Shellcodes
MITM Attack with Patching Binaries on the Fly by Adding ShellcodesMITM Attack with Patching Binaries on the Fly by Adding Shellcodes
MITM Attack with Patching Binaries on the Fly by Adding ShellcodesGianluca Gabrielli
 
Software libero nei sistemi embedded
Software libero nei sistemi embeddedSoftware libero nei sistemi embedded
Software libero nei sistemi embeddedDaniele Costarella
 
[SLIDE]Modellazione della dinamica di un liquido bifase mediante GPU CUDA
[SLIDE]Modellazione della dinamica di un liquido bifase mediante GPU CUDA[SLIDE]Modellazione della dinamica di un liquido bifase mediante GPU CUDA
[SLIDE]Modellazione della dinamica di un liquido bifase mediante GPU CUDAkylanee
 
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1Daniele Falamesca
 

Similar to Summary of “The Case for Writing Network Drivers in High-Level Programming Languages" (20)

Summary of “The Case for Writing Network Drivers in High-Level Programming La...
Summary of “The Case for Writing Network Drivers in High-Level Programming La...Summary of “The Case for Writing Network Drivers in High-Level Programming La...
Summary of “The Case for Writing Network Drivers in High-Level Programming La...
 
Progettazione e sviluppo di un software applicativo su un single board computer
Progettazione e sviluppo di un software applicativo su un single board computerProgettazione e sviluppo di un software applicativo su un single board computer
Progettazione e sviluppo di un software applicativo su un single board computer
 
Webinar porting e ottimizzazione per x86
Webinar   porting e ottimizzazione per x86Webinar   porting e ottimizzazione per x86
Webinar porting e ottimizzazione per x86
 
Presentazione tesi 2.0
Presentazione tesi 2.0Presentazione tesi 2.0
Presentazione tesi 2.0
 
Thanatos
ThanatosThanatos
Thanatos
 
Tesi Todone
Tesi TodoneTesi Todone
Tesi Todone
 
J2Me Il Micro Mondo Java
J2Me Il Micro Mondo JavaJ2Me Il Micro Mondo Java
J2Me Il Micro Mondo Java
 
3rd 3DDRESD: BSS
3rd 3DDRESD: BSS3rd 3DDRESD: BSS
3rd 3DDRESD: BSS
 
Thesis Frascino Slide iT
Thesis Frascino Slide iTThesis Frascino Slide iT
Thesis Frascino Slide iT
 
PIT2012: Workshop@UniNA - Compilazione del Kernel Linux
PIT2012: Workshop@UniNA - Compilazione del Kernel LinuxPIT2012: Workshop@UniNA - Compilazione del Kernel Linux
PIT2012: Workshop@UniNA - Compilazione del Kernel Linux
 
Trace32 lo-strumento-piu-completo-per-il-debug-di-un-sistema-linux
Trace32 lo-strumento-piu-completo-per-il-debug-di-un-sistema-linuxTrace32 lo-strumento-piu-completo-per-il-debug-di-un-sistema-linux
Trace32 lo-strumento-piu-completo-per-il-debug-di-un-sistema-linux
 
Generazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGenerazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptx
 
GNU Linux Programming introduction
GNU Linux Programming introductionGNU Linux Programming introduction
GNU Linux Programming introduction
 
PALUZZANO TESI
PALUZZANO TESIPALUZZANO TESI
PALUZZANO TESI
 
MITM Attack with Patching Binaries on the Fly by Adding Shellcodes
MITM Attack with Patching Binaries on the Fly by Adding ShellcodesMITM Attack with Patching Binaries on the Fly by Adding Shellcodes
MITM Attack with Patching Binaries on the Fly by Adding Shellcodes
 
Software libero nei sistemi embedded
Software libero nei sistemi embeddedSoftware libero nei sistemi embedded
Software libero nei sistemi embedded
 
Database Data Aggregator
Database Data AggregatorDatabase Data Aggregator
Database Data Aggregator
 
[SLIDE]Modellazione della dinamica di un liquido bifase mediante GPU CUDA
[SLIDE]Modellazione della dinamica di un liquido bifase mediante GPU CUDA[SLIDE]Modellazione della dinamica di un liquido bifase mediante GPU CUDA
[SLIDE]Modellazione della dinamica di un liquido bifase mediante GPU CUDA
 
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
Corso di Basi e Fondamenti di Programmazione in C++ Lezione 1
 
Low Level Software Security
Low Level Software SecurityLow Level Software Security
Low Level Software Security
 

Recently uploaded

Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI GiovanniGiornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI GiovanniServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA GiorgioGiornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA GiorgioServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' DavideGiornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' DavideServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO AndreaGiornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO AndreaServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO AntonioGiornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO AntonioServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI DanieleGiornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI DanieleServizi a rete
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO SimoneGiornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO SimoneServizi a rete
 

Recently uploaded (7)

Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI GiovanniGiornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | CADEI Giovanni
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA GiorgioGiornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | SERRA Giorgio
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' DavideGiornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ROMANO' Davide
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO AndreaGiornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO AntonioGiornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DISCIPIO Antonio
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI DanieleGiornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | RENZI Daniele
 
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO SimoneGiornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
Giornata Tecnica da Piave Servizi, 11 aprile 2024 | DI DOMENICO Simone
 

Summary of “The Case for Writing Network Drivers in High-Level Programming Languages"

  • 1. UNIVERSITÀ DEGLI STUDI DI TRIESTE DIPARTIMENTO DI INGEGNERIA E ARCHITETTURA Corso di Studi in Ingegneria Elettronica e Informatica Summary of “The Case for Writing Network Drivers in High-Level Programming Languages" [1] Tesi di Laurea Triennale Laureando: Leonardo IURADA Relatore: prof. Marco TESSAROTTO ANNO ACCADEMICO 2019/2020
  • 2. Indice 1 Introduzione 1 2 Metodo d’indagine 2 2.1 Architettura del Device Driver . . . . . . . . . . . . . . . . . . . 2 2.2 Implementazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2.3 Test Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 Risultati 4 3.1 Prestazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 3.2 Il costo delle funzionalità di sicurezza . . . . . . . . . . . . . . . . 4 3.3 Latenza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 4 Conclusione 6
  • 3. 1 Introduzione C è stato il linguaggio di programmazione prediletto per scrivere i kernel sin dal suo concepimento. Anche la maggior parte dei driver dei dispositivi sono scritti in C, o con un sottoinsieme ristretto di C++ fornendo praticamente nessuna funzionalità di sicurezza aggiuntiva. Sono stati trovati 65 bug di sicurezza in Linux nel 2017 di cui: 8 sono bug use-after-free o bug double-free, 32 sono bug che permettono di eseguire accessi out-of-bounds, 14 sono bug logici e 11 sono bug il cui effetto rimane ancora poco chiaro. I driver rappresentano il 66% del codice in Linux, ma 39 su 40 di questi bug di sicurezza relativi alla memory safety sono localizzati nei driver e possono essere mitigati usando un linguaggio di programmazione che imponga dei controlli di sicurezza. Questo studio si propone di valutare la possibilità di utilizzare linguaggi di programmazione ad alto livello per scrivere driver a livello di spazio utente, scegliendo come punto di partenza di studiare il comportamento di tali linguaggi nei driver di rete. I linguaggi ad alto livello infatti sono più sicuri (meno bug, più controlli di sicurezza), ma i safety checks al runtime riducono il throughput e la garbage collection porta a picchi di latenza. Quindi, questo studio si propone di far luce su due aspetti principali di questo problema: valutare quali linguaggi siano adatti allo sviluppo di user space driver e confrontare costi e benefici dei vari aspetti di sicurezza apportati da tali linguaggi. La scelta di passare da driver kernel a user space driver è dettata dal fatto che i driver sono anche le superfici d’attacco più grandi (in termini di righe di codice) nei moderni sistemi operativi e continuano a crescere in complessità man mano che vengono aggiunte ulteriori funzionalità. Gli user space driver inoltre sono molto più isolati dal resto del sistema rispetto ai kernel driver: eseguire un accesso out-of-bounds è ancora un bug in alcuni linguaggi memory-safe, ma fa sì che il programma vada in crash se non gestito; declassando una vulnerabilità riguardante l’esecuzione arbitraria di codice in un denial of service (DoS). Gli user space driver possono semplicemente essere riavviati dopo un crash, mentre un crash di un driver kernel di solito fa sì che l’intero sistema collassi. La decisione di studiare il comportamento dei driver di rete non è casuale: negli ultimi anni, i driver dei dispositivi di rete sono riusciti a sfuggire dal- le grinfie del kernel grazie agli user space driver (esempio, DPDK, framework per costruire applicativi in grado di processare pacchetti in maniera rapida, costituito da diversi user space driver). 1
  • 4. 2 Metodo d’indagine Per indagare la questione, centro di questa ricerca, è stato scelto di utilizzare Linux x86 e sono state presentate implementazioni di uno user space driver ottimizzato per le prestazioni per la famiglia di controller di rete “Intel ixgbe” (82599ES, X540, X550, e X552) in 9 linguaggi ad alto livello (Rust, Go, C, Java, OCaml, Haskell, Swift, JavaScript, e Python) le cui caratteristiche principali sono riassunte nella Tabella 1. Tutte le implementazioni sono scritte da zero da programmatori esperti nei rispettivi linguaggi di programmazione in stile idiomatico e seguono la stessa architettura di base, permettendo un confronto in termini di prestazioni tra i linguaggi ad alto livello e l’implementazione di riferimento in C. Linguaggio Paradigma principale∗ Memory mgmt. Compilazione Rust Imperative Ownership/RAII† Compilato‡ Go Imperative Garbage collection Compilato C# Object-oriented Garbage collection JIT Java Object-oriented Garbage collection JIT OCaml Functional Garbage collection Compilato Haskell Functional Garbage collection Compilato‡ Swift Protocol-oriented Reference counting Compilato‡ JavaScript Imperative Garbage collection JIT Python Imperative Garbage collection Interpretato ∗ Tutti i linguaggi selezionati sono multi-paradigma † L’acquisizione delle risorse è l’Inizializzazione ‡ Usando LLVM Tabella 1: Linguaggi usati nelle implementazioni 2.1 Architettura del Device Driver L’architettura del driver ixy (la cui versione di riferimento è scritta in C) è ispirata al DPDK, vale a dire, usa driver in modalità polling senza interrupts, tutte la API si basano sul processare pacchetti in batch, e sfruttano l’UIO subsystem. Nei linguaggi in cui le syscall mmap e mlock non sono disponibili, vengono sfruttate piccole funzioni in C. Inoltre, in ogni linguaggio sono stati pensati wrapper specifici per lo spazio d’indirizzi MMIO PCIe e per i DMA buffer in modo da poter fornire un mecca- nismo di boundary checking e permettere l’accesso alla memoria senza causare violazioni. In ogni caso, sia l’uso di mmap che l’uso di struct costruiti a partire da un puntatore e una lunghezza sono operazioni intrinsecamente non sicure. L’obiettivo è quello di ridurre queste operazioni a meno aree di codice possibile per ridurre la quantità di codice da monitorare manualmente per evitare errori di memoria. Ultima considerazione sull’architettura del driver: mentre in C è prevista la keyword volatile per evitare che le ottimizzazioni introdotte dal compila- tore vadano ad introdurre comportamenti non voluti nell’esecuzione del codice (esempio, leggere ripetutamente lo stesso registro in un loop mentre si attende che un valore venga modificato dal dispositivo sembra un’opportunità per l’ot- timizzatore di sollevare la lettura dal loop), nei linguaggi ad alto livello possono essere usate utility di gestione della concorrenza. 2
  • 5. 2.2 Implementazioni Tutte le implementazioni nei linguaggi ad alto livello hanno richiesto più righe di codice rispetto a C. La Tabella 2 riassume le protezioni contro le classi di bug disponibili sia per le implementazioni dei driver che per le applicazioni basate su di essi. Il vantaggio qui è che linguaggi ad alto livello non aumentano necessariamente la difficoltà d’implementazione per il programmatore ottenendo vantaggi in termini di sicurezza. Memoria Generale Packet Buffer Ling. OoB1 Use after free OoB1 Use after free Int overflows C Rust ()2 ()5 Go ()2 ()4 C# ()2 ()4 ()5 Java ()2 ()4 OCaml ()2 ()4 Haskell ()2 ()4 ()6 Swift 3 ()4 JavaScript ()2 ()4 ()6 Python ()2 ()4 ()6 1 Accesso Out of Bounds. 2 Bounds imposti da wrapper, constructor in codice unsafe o C. 3 Bounds imposti solo in debug mode. 4 Buffer mai liberati/garbage collected, solo ritornati ad una memory pool. 5 Disabilitato di default. 6 Di default usa floating point precision anche per gli integer. Tabella 2: Protezioni a livello di linguaggio contro le classi di bug nei driver. L’unica implementazione non ottimizzata per alte prestazioni è quella in Py- thon: essa è stata pensata come un semplice strumento educativo. Nella Tabella 3 riassumiamo i principali metodi di wrapping usati nelle implementazioni. Linguaggio Gestione ed Accesso in Memoria Wrapper Memoria Esterna Rust Ownership system std::slice objects ptr module Rust’s struct Go atomic package slice data type C# Unsafe mode Marshal in System.Runtime.InteropServices Java sun.misc.Unsafe Object Java Class OCaml C helper functions OCaml’s Bigarrays Cstruct library Haskell System.Posix.Memory Foreign package Swift Automatic Reference counting UnsafeBufferPointers JavaScript Node.js module in C JS’ ArrayBuffers Python Cython Python Class Tabella 3: Metodi di wrapping in ogni implementazione 2.3 Test Setup Per stimare le prestazioni del routing (misurate in Mpps), i driver vengono eseguiti su una CPU Xeon E3-1230 v2 con clock a 3.3GHz con due 10Gbit/s Intel X520 NIC. Il traffico di test è generato con MoonGen. I due NIC eseguono 3
  • 6. un’applicazione di forwarding che modifica un byte nell’header del pacchetto. Tutti i test sono ristretti all’uso di un singolo core della CPU. Per valutare la latenza invece, viene eseguito il mirroring di tutti i pacchetti per mezzo di uno splitter in fibra ottica su un server che esegue MoonSniff con timestamp registrati a livello hardware su una NIC integrata Xeon D (precisione misurata 24 ns, senza perdite di pacchetti). Il dispositivo sotto test usa un Intel Xeon E5-2620 v3 a 2.40 GHz e un dual-port Intel X520 NIC. Tutte le latenze sono state misurate con una dimensione batch di 32 sotto carico bidirezionale con traffico a bit rate costante. 3 Risultati 3.1 Prestazioni Processare pacchetti in batch è un concetto chiave in tutti i veloci driver di rete. Ogni batch ricevuta o inviata richiede la sincronizzazione con la scheda di rete, batch più grandi incrementano quindi le prestazioni. Batch troppo grandi riempiono le cache della CPU quindi si hanno pochi miglioramenti o addirittura una riduzione delle prestazioni. Da 32 a 128 pacchetti per batch è la scelta ottima per gli user space driver. La Figura 1 mostra le prestazioni massime raggiunte nel forwarding bidirezionale dalle varie implementazioni. Haskell e OCaml allocano una nuova lista/array per ogni batch di pacchetti che viene processata mentre tutti gli altri linguaggi riutilizzano gli array. Riciclare gli array in questi linguaggi funzionali violerebbe la volontà di scrivere codice in stile idiomatico, questa è una delle ragioni per le loro prestazioni più basse. Figura 1: Forwarding Rate on 3.3 GHz CPU. 3.2 Il costo delle funzionalità di sicurezza Rust è l’implementazione più rapida che raggiunge oltre il 90% delle prestazioni di C. È anche l’unico linguaggio di alto livello senza costi generali per la gestione della memoria, che lo rende un candidato ideale per indagini ulteriori. 4
  • 7. Eventi per pacchetto C Rust Cicli 94 100 Istruzioni 127 209 Istr. per ciclo 1.35 2.09 Branches 18 24 Branches mispredicts 0.05 0.08 Store µops 21.8 37.4 Load µops 30.1 77.0 Load L1 hits 24.3 75.9 Load L2 hits 1.1 0.05 Load L3 hits 0.9 0.0 Load L3 misses 0.3 0.1 Tabella 4: Prestazioni in eventi per pacchetto. Batch 32, 1.6 GHz. Lo svantaggio di performance principale è dato dal boundary chec- king. La Tabella 4 elenca i risulta- ti in eventi per pacchetto inoltrato. Rust richiede il 65% di istruzioni in più per inoltrare un singolo pacchet- to a una dimensione di batch di 32. Il numero di branch eseguite aumen- ta del 33%, il numero di load anche del 150%. Tuttavia, il codice Rust ri- chiede solo il 6% di cicli in più per pacchetto nel complesso nonostante faccia più lavoro. Una moderna su- perscalar CPU con esecuzione out-of- order può effettivamente nascondere l’overhead introdotto dai controlli di sicurezza: il processore è in grado di prevedere correttamente (il tasso di errore del branch è allo 0,2% - 0,3%) ed ese- guire speculativamente il percorso corretto. Un’altra caratteristica di sicurezza di Rust sono i controlli sugli integer overflow: devono tuttavia essere attiva- ti esplicitamente con una flag al momento della compilazione. Facendo così si decrementa il throughput di solo lo 0.8% con batch di dimensione 8, nessuna deviazione statistica significativa è stata misurata con batch di dimensione più grande. Anche in questo caso la speculative execution nascondendo il costo delle caratteristiche di sicurezza. 3.3 Latenza La latenza è dominata dal tempo impiegato nel buffer, non dal tempo impiegato nella gestione di un pacchetto dalla CPU. I driver inoltrano i pacchetti in centi- naia di cicli, ovvero entro centinaia di nanosecondi. Pertanto, un driver con un throughput inferiore non è automaticamente uno con una latenza più elevata durante il funzionamento al limite massimo del carico di lavoro. I principali fat- tori che determinano l’aumento della latenza sono le pause dovute alla garbage collection e alle dimensioni della batch. I driver funzionano con un buffer di dimensione 512 di default e configurano la NIC per eliminare i pacchetti se il buffer di ricezione è pieno. La Figura 2 mostra le latenze di coda dei driver durante l’inoltro di pacchetti a velocità diverse. I dati vengono tracciati come CCDF per concentrarsi sulle latenze nel caso peggiore. 5
  • 8. Figura 2: Latenze di coda durante l’inoltro dei pacchetti. La loro latenza è semplicemente una funzione della dimensione del buffer di ricezione man mano che esso si riempie completamente. Nessuna latenza mas- sima osservata è significativamente diversa dal 99.9999-esimo percentile. Java e JavaScript perdono i pacchetti durante l’avvio a causa della compilazione JIT, pertanto escludiamo i primi 5 secondi dell’esecuzione del test per questi due linguaggi. Tutti i test mostrati sono stati eseguiti senza perdita di pacchetti. 4 Conclusione Riscrivere l’intero sistema operativo in un linguaggio di alto livello è uno sforzo lodevole ma difficilmente rimpiazzerà l’utilizzo dei sistemi operativi desktop e server tradizionali nel prossimo futuro. Si propone di iniziare a riscrivere i driver come user space driver in linguaggi di alto livello poiché 39 dei 40 bug di sicurezza della memoria di Linux elencati si trovano nei driver, a dimostrazione del fatto che la maggior parte dei miglioramenti in termini di sicurezza possono essere ottenuti senza sostituire l’intero sistema operativo. I driver di rete sono un buon punto di partenza per questo sforzo: i driver di rete a livello di spazio utente scritti in C sono già all’ordine del giorno (esempio, DPDK). Dai test eseguiti emerge che Rust è il primo candidato come linguaggio per driver più sicuri: il suo ownership system previene bug di memoria anche in aree di memoria personalizzate non allocate dal runtime del linguaggio. Il costo di questi aspetti di sicurezza sono solo il 2% - 10% del throughput su moderne superscalar CPU out-of-order. L’ownership system di Rust fornisce più funzionalità di sicurezza rispetto ai linguaggi basati sulla garbage collection senza influenzare la latenza. Anche Go e C# sono dei linguaggi adatti se il sistema è in grado di far fronte a picchi di latenza inferiori al millisecondo dovuti alla garbage collection. Gli altri linguaggi discussi qui possono anche essere utili se le prestazioni sono meno critiche rispetto ad avere un sistema sicuro e corretto, ad esempio Haskell e OCaml sono più adatti alla verifica formale. Riferimenti bibliografici [1] Paul Emmerich, Simon Ellmann, Fabian Bonk, Alex Egger, Esaú García Sánchez- Torija, Thomas Günzel, Sebastian Di Luzio, Alexandru Obada, Maximilian Stadl- meier, Sebastian Voit, Georg Carle The Case for Writing Network Drivers in High-Level Programming Languages ANCS’ 19, 13 September 2019 6