Linux proc(5)
manuálová stránka mi říká, že /proc/$pid/mem
„lze použít k přístupu na stránky paměti procesu“. Ale přímý pokus o jeho použití mi dává pouze
$ cat /proc/$$/mem /proc/self/mem
cat: /proc/3065/mem: No such process
cat: /proc/self/mem: Input/output error
Proč není cat
dokáže vytisknout vlastní paměť (/proc/self/mem
)? A co je to za podivnou chybu „žádný takový proces“, když se pokouším vytisknout paměť shellu (/proc/$$/mem
, proces evidentně existuje)? Jak mohu číst z /proc/$pid/mem
, tedy?
Přijatá odpověď:
/proc/$pid/maps
/proc/$pid/mem
zobrazuje obsah paměti $pid mapovaný stejným způsobem jako v procesu, tj. bajt na offsetu x v pseudosouboru je stejný jako bajt na adrese x v průběhu. Pokud je adresa v procesu nenamapována, čtení z odpovídajícího offsetu v souboru vrátí EIO
(Chyba vstupu/výstupu). Například, protože první stránka v procesu není nikdy mapována (takže dereferencování NULL
ukazatel selže čistě, místo aby neúmyslně přistupoval ke skutečné paměti), čte první bajt z /proc/$pid/mem
vždy způsobí chybu I/O.
Způsob, jak zjistit, jaké části paměti procesu jsou mapovány, je přečíst si /proc/$pid/maps
. Tento soubor obsahuje jeden řádek na mapovanou oblast a vypadá takto:
08048000-08054000 r-xp 00000000 08:01 828061 /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0 [heap]
První dvě čísla jsou hranice oblasti (adresy prvního bajtu a bajtu za posledním, v hexa). Další sloupec obsahuje oprávnění, pak jsou zde nějaké informace o souboru (offset, zařízení, inode a název), pokud se jedná o mapování souboru. Viz proc(5)
man page nebo Understanding Linux /proc/id/maps, kde najdete další informace.
Zde je skript proof-of-concept, který vypíše obsah své vlastní paměti.
#! /usr/bin/env python
import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'rb', 0)
output_file = open("self.dump", 'wb')
for line in maps_file.readlines(): # for each mapped region
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
if m.group(3) == 'r': # if this is a readable region
start = int(m.group(1), 16)
end = int(m.group(2), 16)
mem_file.seek(start) # seek to region start
chunk = mem_file.read(end - start) # read region contents
output_file.write(chunk) # dump contents to standard output
maps_file.close()
mem_file.close()
output_file.close()
/proc/$pid/mem
[Následuje pro historické zajímavosti. Neplatí pro aktuální jádra.]
Od verze 3.3 jádra máte přístup k /proc/$pid/mem
normálně, pokud k němu přistupujete pouze s namapovanými offsety a máte oprávnění je sledovat (stejná oprávnění jako ptrace
pro přístup pouze pro čtení). Ale ve starších jádrech se objevily další komplikace.
Pokud se pokusíte číst z mem
pseudosoubor jiného procesu, nefunguje to:dostanete ESRCH
(Žádný takový proces) chyba.
Oprávnění na /proc/$pid/mem
(r--------
) jsou liberálnější, než by mělo být. Například byste neměli být schopni číst paměť setuid procesu. Kromě toho pokus o načtení paměti procesu, zatímco proces ji upravuje, by mohl čtenáři poskytnout nekonzistentní pohled na paměť, a co je horší, existovaly rasové podmínky, které mohly sledovat starší verze linuxového jádra (podle tohoto vlákna lkml, ačkoli jsem neznám podrobnosti). Jsou tedy nutné další kontroly:
- Proces, který chce číst z
/proc/$pid/mem
musí se připojit k procesu pomocíptrace
pomocíPTRACE_ATTACH
vlajka. To je to, co debuggery dělají, když začnou ladit proces; je to také to, costrace
provádí systémová volání procesu. Jakmile čtečka dokončí čtení z/proc/$pid/mem
, měl by se odpojit volánímptrace
pomocíPTRACE_DETACH
vlajka. - Pozorovaný proces nesmí běžet. Normálně volá
ptrace(PTRACE_ATTACH, …)
zastaví cílový proces (odešleSTOP
signál), ale existuje spor (doručování signálu je asynchronní), takže sledovač by měl zavolatwait
(jak je zdokumentováno vptrace(2)
).
Proces běžící jako root může číst paměť libovolného procesu, aniž by musel volat ptrace
, ale pozorovaný proces musí být zastaven, jinak bude čtení stále vracet ESRCH
.
Ve zdrojovém kódu linuxového jádra kód poskytující položky pro jednotlivé procesy v /proc
je v fs/proc/base.c
a funkce ke čtení z /proc/$pid/mem
je mem_read
. Dodatečnou kontrolu provádí check_mem_permission
.
Zde je ukázkový kód C, který lze připojit k procesu a přečíst si jeho část mem
soubor (kontrola chyb vynechána):
sprintf(mem_file_name, "/proc/%d/mem", pid);
mem_fd = open(mem_file_name, O_RDONLY);
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
waitpid(pid, NULL, 0);
lseek(mem_fd, offset, SEEK_SET);
read(mem_fd, buf, _SC_PAGE_SIZE);
ptrace(PTRACE_DETACH, pid, NULL, NULL);
Již jsem zveřejnil skript proof-of-concept pro dumping /proc/$pid/mem
v jiném vláknu.