RPM durch Softwarepakete angreifbar

Das ursprünglich von Red Hat entwickelte RPM ist eines der am weitesten verbreiteten Paketverwaltungssysteme. Eine kürzlich entdeckte Sicherheitslücke in diesem Paketmanager hat zur Folge, dass ein entfernter Angreifer einem potenziellen Opfer speziell präparierte RPM-Dateien unterjubeln kann, die möglicherweise dazu führen, dass Befehle mit den Rechten des Anwenders ausgeführt werden. Diese Schwachstelle betrifft auch Librpm-basierte Applikationen wie Yum.

RPM-Dateien bestehen aus vier verschiedenen Teilen: dem Lead (Datei-Identifier), der Signatur, dem Header und dem eigentlich Archiv (den Daten). Der Lead wird verwendet, um die RPM-Datei anhand einer Magic Number als solche zu erkennen. Damit ist es für Programme sehr schnell möglich, zu sehen, ob es sich um eine RPM-Datei handelt oder nicht. Die darauffolgende Signatur stellt die Integrität der Daten sicher, und kann optional auch zur Authentifikation verwendet werden. Diese Signatur wird über den Header und das Archiv der Datei berechnet, wobei beispielsweise PGP oder MD5 zum Einsatz kommt. Der Header besteht aus einem Header-Record, ein oder mehreren Header-Index-Record-Strukturen und den Daten für diese Index-Strukturen. Die
eigentlichen Programmdaten der RPM-Datei sind als Archiv abgelegt.

Die Sicherheitslücke resultiert aus einem Programmierfehler in der Funktion “regionSwab()” in der Datei “lib/header.c”. Dieser Fehler lässt sich mit Hilfe eines speziellen RPM-Datei-Headers auslösen, und ist mit der kürzlich veröffentlichten Exploit-RPM-Datei regionSwab.rpm rekonstruierbar.

Ein einfacher Aufruf von “rpm –checksig regionSwab.rpm” genügt, um einen Absturz herbeizuführen:

mark@tux:~$ rpm --checksig regionSwab.rpm
error: cannot open Name index using db3 - No such file or directory (2)
*** glibc detected *** rpm: free(): invalid pointer: 0x0000000001164ad4 ***

Die Glibc-Fehlermeldung “free(): invalid pointer” deutet schon an, wo hier das Problem liegt: ein “free()”-Aufruf zum Freigeben zuvor allozierten Speichers wurde auf einen nicht gültigen Speicherzeiger aufgerufen. Um herauszufinden, wo der “free()”-Aufruf fehlschlägt, eignet sich der GNU-Debugger:

mark@tux:~$ gdb rpm
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/bin/rpm...(no debugging symbols found)...done.
(gdb) r --checksig regionSwab.rpm
Starting program: /usr/bin/rpm --checksig regionSwab.rpm
[Thread debugging using libthread_db enabled]
error: cannot open Name index using db3 - No such file or directory (2)
*** glibc detected *** /usr/bin/rpm: free(): invalid pointer: 0x000000000064fad4 ***
======= Backtrace: =========
/lib/libc.so.6(+0x775b6)[0x7ffff66755b6]
/lib/libc.so.6(cfree+0x73)[0x7ffff667be83]
/usr/lib/librpm.so.0(headerFree+0xbc)[0x7ffff795bfec]
/usr/lib/librpm.so.0(+0x3ab09)[0x7ffff797eb09]
/usr/lib/librpm.so.0(rpmVerifySignatures+0x239)[0x7ffff797f8a9]
/usr/lib/librpm.so.0(rpmcliSign+0x138)[0x7ffff7980338]
/usr/bin/rpm[0x402ddc]
/lib/libc.so.6(__libc_start_main+0xfd)[0x7ffff661cc4d]
/usr/bin/rpm[0x402019]
======= Memory map: ========
00400000-00405000 r-xp 00000000 08:01 272123 /usr/bin/rpm
00604000-00605000 r--p 00004000 08:01 272123 /usr/bin/rpm
00605000-00606000 rw-p 00005000 08:01 272123 /usr/bin/rpm
00606000-00666000 rw-p 00000000 00:00 0 [heap]
[...]

Das Backtrace verrät, wo RPM abstürzte: Der letzte Aufruf ist “free()”, wie schon die Glibc-Fehlermeldung vermuten ließ. Dieser Aufruf wird von der “headerFree()”-Funktion getätigt, die wiederum zuvor von “rpmVerifySignatures()” aufgerufen wird.

Wie entsteht dieser Fehler genau? Dazu muss man wissen, dass der Header der Exploit-RPM-Datei so gestrickt ist, dass in der Funktion “headerLoad()” die Variablen “rdl” und “ril” mit folgendem Code aus der Datei gelesen werden:

rdl = -ntohl(stei[2]); /* negative offset */
ril = rdl/sizeof(*pe);

Diese Variablen sind für das Verarbeiten der Index- und Dateneinträge der RPM-Datei relevant. Der Exploit sorgt nun dafür, dass an dieser Stelle “ril” auf 0 gesetzt wird, was zur Folge hat, dass die darauffolgende Codezeile

rdlen = regionSwab(entry+1, ril-1, 0, pe+1, dataStart, dataEnd, entry->info.offset);

ohne etwas zu tun sofort zurückkehrt, da “ril-1=-1”, und in “regionSwab()” eine dekrementierende For-Schleife von “ril-1” bis 0 läuft. Anschließend wird folgendes aufgerufen:

/* Load dribble entries from region. */
rc = regionSwab(newEntry, ne, 0, pe+ril, dataStart, dataEnd, rid);

Die Variable “ne” ist nicht-negativ, und der Code tritt damit in die For-Schleife in “regionSwab()” ein. Dabei werden aus der RPM-Datei unter anderem “ie.info.offset” und “ie.info.count” ausgelesen und dazu verwendet, “ie.data” und “ie.length” zu berechnen. Die in der RPM-Datei abgelegten Werte sorgen dabei allerdings dafür, dass der Zeiger “entry->data” an eine falsche Stelle geschoben wird. Er zeigt im Normalfall auf einen zuvor allozierten Speicherbereich des Heap, den später die Funktion “headerFree()” via “_free(entry->data)” wieder freigibt. Und hier kommt es zum Absturz, da das Programm versucht, einen falschen Speicherbereich freizugeben. Unter Umständen kann ein Angreifer so auch Befehle ausführen.

Das Patch zur Behebung des Problems fügt zusätzliche Kontrollen in die Funktion “regionSwab()” ein, um das oben beschriebene Szenario zu verhindern.

Aus folgendem Code:

static int regionSwab(indexEntry entry, int il, int dl, entryInfo pe, unsigned char * dataStart, const unsigned char * dataEnd, int regionid)
{ for (; il > 0; il--, pe++) { struct indexEntry_s ie; rpm_tagtype_t type; [...]
}

wird dieser:

static int regionSwab(indexEntry entry, int il, int dl, entryInfo pe, unsigned char * dataStart, const unsigned char * dataEnd, int regionid)
{ if ((entry != NULL && regionid >= 0) || (entry == NULL && regionid != 0)) return -1; for (; il > 0; il--, pe++) { struct indexEntry_s ie; rpm_tagtype_t type; [...]
}

Damit ist die Attacke nicht mehr möglich. Betroffen sind die RPM-Versionen 4.9.1.1 und älter.

Nach oben