Fragen? Antworten! Siehe auch: Alternativlos
Normalerweise kopiert man in C Strings mit strcpy, aber das weiß nicht, wie lang der Buffer ist, und kann daher nichts prüfen. Wenn man einen zu langen String kopiert, kann ein Angreifer damit die Anwendung übernehmen und Ransomware installieren.
C hat auch noch eine andere Funktion zum String-Kopieren, strncpy. Dem gibt man eine Länge, aber das eröffnet direkt die nächste Lücke. Wenn man einen zu langen String kopiert, überschreibt der zwar nichts hinter dem Bufferende, aber er fügt auch kein terminierendes Nullbyte ein. Das führt dann bei der nächsten Verwendung des Buffers zu Fehlern, wenn auch erstmal "nur" Lesefehlern. Kann aber immer noch die Anwendung crashen.
strlcpy ist ähnlich strncpy, aber garantiert ein Nullbyte. Ausnahme: Wenn man eine Bufferlänge 0 übergibt.
Ist damit jetzt alles gut? Nein. Abgeschnittene Strings sind vielleicht keine Speicherkorruption, aber können ebenfalls ein Sicherheitsproblem darstellen. Z.B. wenn ein Angreifer es schafft, in einer Logzeile am Anfang lauter Müll einzufügen, und damit am Ende seine IP abzuschneiden, die dann nicht im Log steht.
Und es bleibt die Frage, was man tun soll, wenn jemand strlcpy mit Länge 0 aufruft. Oder strlcat und es ist kein 0-Terminator im Zielpuffer.
Am Ende kommt man mit solchen Quick Fixes vielleicht dem Ziel etwas näher, aber es nimmt einem doch nicht die Pflicht, ordentlichen Code zu schreiben, und alle Eventualitäten zu beachten.
Dennoch. Die Funktionen sind aus dem letzten Jahrtausend, und glibc war die letzte große Unix-Implementation, die das nicht hatte. Das war schon irgendwie ein bisschen peinlich, weil es halt eher nach einer Trotzreaktion als nach einem inhaltlichen Argument aussah.
pkexec ist ja nur die Quelle der Rechte, aber nicht der Root Cause des Exploits. Wie in https://seclists.org/oss-sec/2022/q1/80 geschildert, ist es ja glibc, genauer iconv, die da sinn- und sorglos .so-Files nachladen und ausführen.Fun fact: Von diesem fopen-css-Ding wusste ich auch noch nichts. Aber ich wusste, dass iconv in der glibc Code nachlädt und daher gefährlich ist, und dass die da von Hand irgendwelche Environment-Variablen filtern, wenn sie glauben, dass sie setuid laufen, und dabei auch schonmal was vergessen haben, und ich wusste, dass glib das alles implizit reinzieht. Ich fand glib auch aus anderen Gründen nie vertrauenswürdig und habe das daher nie in irgeneines meiner Projekte reingezogen. Manchmal ist es eine gute Idee, auf sein Bauchgefühl zu hören.Das ist auch keine neue Beobachtung, in 2014 hat Tavis bereits https://www.openwall.com/lists/oss-security/2014/07/14/1 darauf hingewiesen. Maybe someone can figure out how to turn this into something scary.
Und in 2017 ist der defekte Code in glibc bereits gefixt worden, aber ... in einer anderen Kopie des Codes, https://www.openwall.com/lists/oss-security/2017/06/23/8, in einem vollkommenen Versagen von Code-Handling und Versioning seitens der GNU Leute (also ist niemand überrascht).
Und 2019 hat jemand in https://hugeh0ge.github.io/2019/11/04/Getting-Arbitrary-Code-Execution-from-fopen-s-2nd-Argument/ gezeigt, wie man diesen Code über das 2. Argument von fopen(3) (ja, das "r"-Flag) triggert, also Code Execution von einem fopen("blafasel.txt", "r") bekommt. Okay, es ist ein fopen("blafasel.txt", "r,css=keks"), aber bitte. Das ist alles ganz wunderbar.
Ich programmiere seit 1983, mache C seit 1986 und lernte gestern vom ccs= Flag von fopen(3) in glibc. Ein weiteres Stück Bibliothek, das irgendwo im Dateisystem gefundene .so-Dateien reinlädt und anleckt, um zu sehen, ob man sie ausführen kann.
Ich bin mir sicher, das ist nicht das letzte Mal, daß wir von iconv im Kontext Exploits hören werden.
Es gab da mal eine Zeit, da hat das GNU-Projekt echte Anstrengungen auf sich genommen, um die Anzahl an Abhängigkeiten minimal zu halten.
Ich rede hier nicht von "dann geht 'make test' nicht", nein, das configure bricht direkt ab.
Auf der anderen Seite: Was will man von dem GNU Projekt noch erwarten, nachdem sie gcc auf C++ umgestellt haben. Jetzt braucht man zum Bootstrappen von gcc den Nachweis, dass man gcc nicht mehr braucht, weil man eh schon eine halbwegs moderne Entwicklungsumgebung hat. Völlig absurd.
Wie in den Witzen über Banker und Kredite.
Da passt es auch gut ins Bild, wenn man glibc nicht bauen kann, weil man kein Python hat.
Ich finde ja, ihr braucht noch eine Abhängigkeit auf .NET oder Java. Ach was sage ich. .NET und Java!
Auf der anderen Seite wissen dann auch Hacker, wo was ist. Und weil die Softwareentwicklungsbranche vor vielen Jahren entschieden hat, dass sauber Programmieren zu schwer ist und wir lieber Hacker ärgern wollen, ging die Entwicklung dann dahin, dass man das Programm halt bei jedem Programmstart woanders hin tut im Adressraum. Das nennt sich dann PIE.
Technisch läuft das über einen gruseligen Hack im ELF-Dateiformat, denn das kennt nur Binary (Programm, an statischer Adresse) und Shared Object (Shared Library, an dynamischer Adresse). Das ist schlicht nicht vorgesehen, dass ein Programm an einer zufälligen Adresse geladen wird. Selbst wenn man den Code so formuliert, dass das ginge (was bei Intel/AMD bei 64-bit-Programmen viel einfacher und effizienter geworden ist als bei 32-bit-Programmen, weil der Befehlssatz da ein anderer ist, der darauf ausgelegt ist). Kurz gesagt gibt es bei 64-bit-Programmen eigentlich keine Effizienz-Ausrede mehr, um die nicht positionsunabhängig zu machen. Es gibt natürlich, wenn man genau hinguckt, doch einen Performancenachteil, aber den will ich hier mal außen vor lassen :-)
Der Hack ist jetzt so, dass man in der Datei einträgt, es sei eine Shared Library, kein Programm. Der Kernel lädt die trotzdem und alles ist gut. Das ist leider ein Opt-In-Ding. Wenn ihr unter Linux Software baut, tut mal bitte immer schön -fPIE auf die Compiler-Kommandozeile.
Nun bin ich aber nicht an regulären Binaries interessiert, sondern an statischen Binaries (das ist die Zielgruppe für meine libc). Und der Hack mit dem PIE geht zwar vom Dateiformat her auch bei statisch gelinkten Programmen, aber in der Praxis halt nicht. Ich habe mal ein paar Tage lang versucht, das doch zum Laufen zu kriegen, und bin am Ende gescheitert. Es geht aber prinzipiell. Das weiß ich, weil es jemand anderes geschafft hat: Der Autor der musl-libc. Der gute Mann hat irgendwann bei GNU ein paar Bugs eingetragen, ob es nicht toll wäre, wenn man da nicht so gruselig hacken müsste, wie er es getan hat. Und gcc 8 hat jetzt die Option -static-pie, die genau das tun soll.
Die wollte ich also auch direkt mal ausprobieren, und baute erstmal die neuen binutils, dann den neuen gcc, und jetzt die neue glibc. Und dann fiel mir auf, dass plötzlich meine dietlibc-Binaries größer waren. /bin/true macht im Wesentlichen nichts und sollte eigentlich nur ein paar Bytes groß sein (ein paar ELF-Header brauchen Binaries schon, aber so Größenordnung 200 Bytes ist eigentlich realistisch). War es aber nicht, sondern es war plötzlich über 4k groß. Gut, es war auch vorher weit davon entfernt, weil die dietlibc inzwischen eben eine Menge anderen Kram supportet, der die Startup-Code größer macht. Z.B. den Stack Protector initialisieren, und Thread Local Storage. Aber das true-Binary hätte trotzdem deutlich unter 4k sein müssen.
Ich habe also ein bisschen rumgeguckt, und es stellte sich raus, dass das nur mit dem binutils-bfd-ld so war. binutils ist das Paket unter Linux, aus dem der Assembler und der Linker kommen. binutils hat aber zwei Linker. Den alten mit bfd (der fetten Abstraktionsschicht von binutils) und den neuen, in C++ geschriebenen, der weniger kann, das dafür angeblich besser: GNU gold. Den habe ich normalerweise nicht im Einsatz, weil der bei mir vor Jahren mal Probleme gemacht hat beim Firefox-Linken. Egal. Mit dem gold ist das true-Binary nur 1,3 KB groß. Ich habe also mal reingeguckt und fand in dem statischen Binary von dem bfd-ld eine PLT. PLT steht für Procedure Linkage Table und ist für die Zusammenarbeit (und Umbiegbarkeit mittels LD_PRELOAD) von Shared Libraries da. Das hat in einem statischen Binary nichts verloren. Ich habe mal einen Bug aufgemacht bei binutils.
Ich stellte also auf GNU gold um, und wollte fluchs die neue glibc bauen — aber die baut mit ihrem neuen static-pie-Support für gcc 8 nicht mit gold, nur mit dem bfd-ld. *SEUFZ*
Aber jetzt wo die Infrastruktur da Support für hat, hoffe ich mal, dass auch für die dietlibc static PIE nur noch eine Frage der Zeit ist, möglichst kurzer Zeit. Das will man schon haben, besonders auf 64-bit-x86-Systemen, wo das kaum noch was kostet gegenüber Nicht-PIE.
Aus Frust hab ich jetzt mal true und false in Assembler gehackt für x86/x64 :-)
Update: Stellt sich raus: static pie mit gcc 8 funktioniert auf Anhieb, auch mit dietlibc. Cool!
Update: Gestern Bug gefiled, heute Patch da. Binutils rockt!
Der Intel Compiler kommt mit neueren glibc Versionen nicht zurecht, da diese einen Bug im Intel Compiler exponieren, der zu "unpredictable system behaviour" führt. Bei uns auf dem HPC Cluster hat sich das so manifestiert, dass unsere Benutzer nach dem Update von CentOS 7.3 auf CentOS 7.4 (dieses Update der glibc exponiert den Intel Bug) bei Simulationen (z.B. mit Kommerziellen "Finite Element" Applikationen wie ANSYS CFX, 3DS Abaqus etc.) falsche Resultate bekamen:Und weil das so grandios ist, kommt hier die technische Erklärung, was da vor sich geht:
- Simulationen die vor dem Update konvergierten tun dies nicht mehr
- Simulationen brechen ab, weil an verschiedenen Stellen NaN's raus kommen wo das nicht sein sollte.
- Bei Simulationen kommen andere Zahlen raus als vor dem Update
Der Bug betrifft potenziell alle mit Intel (Versionen älter als 17.0 Update 5) kompilierten Binaries und Bibliotheken auf Systemen mit Intel CPUs, welche AVX unterstützen.
According to x86-64 psABI, xmm0-xmm7 can be used to pass functionKeine weiteren Fragen, Euer Ehren!
parameters. But ICC also uses xmm8-xmm15 to pass function parameters
which violates x86-64 psABI. As a workaround, you can set environment
variable LD_BIND_NOW=1 by# export LD_BIND_NOW=1
Update: Ich sollte vielleicht mal erklären, was hier vor sich geht. Das ABI ist die Spezifikation dafür, wie man auf einer gegebenen Plattform auf Maschinencode-Ebene Argumente an Funktionen übergibt, und welche Registerinhalte Funktionen überschreiben dürfen, welche sie sichern müssen. Intel hat die Spec gesehen und gesagt "Hold my beer! Die Register dahinten benutzt keiner! Die nehm ich mal!" Das ABI hat aber ganz klar gesagt, dass die eben nicht frei sind.
Das Szenario hier ist: Der Compiler generiert Code für einen Funktionsaufruf. Nun könnte man sagen, hey, wenn der Intel-Compiler beide Seiten erzeugt hat, dann kann ja nichts schiefgehen. Aber es kann halt doch was schiefgehen. Wenn man ein dynamisch gelinktes Binary hat, dann geht der Funktionsaufruf eben nicht zur Funktion, sondern zum Wert in einer Tabelle. Die Einträge in der Tabelle zeigen initial auf ein Stück Code in der glibc, der dann die Adresse für das Symbol herausfindet und in die Tabelle einträgt und dann dahin springt. Die Idee ist, dass so eine Tabelle mehrere Zehntausend Einträge enthalten kann bei großen Binaries, und wenn man die alle beim Start von dem Binary auflöst, dann ergibt das eine spürbare Verzögerung. Denkt hier mal an sowas wie Firefox oder clang von LLVM. Daher löst man erst beim ersten Aufruf auf. Mit LD_BIND_NOW=1 kann man der glibc sagen, dass er bitte alles am Anfang auflösen soll, nicht erst später.
Was hier also passiert ist, ist dass der glibc-Code, der die Symbolauflösung macht, sich an das ABI gehalten hat und die u.a. für ihn reservierte Register benutzt hat. Und da hatte der Intel-Compiler aber Argumente reingetan. Die hat der Code zum Symbolauflösen dann mit Müll überschrieben. Die glibc wäscht hier ihre Hände in Unschuld (und ich hätte nicht gedacht, dass ich DAS nochmal sagen würde). (Danke, Samuel)
Security related changes: CVE-2009-5064: The ldd script would sometimes run the program under examination directly, without preventing code execution through the dynamic linker. (The glibc project disputes that this is a security vulnerability; only trusted binaries must be examined using the ldd script.)
Na ein Glück, dass wir das mal geklärt haben! Das wird nicht Jedem klar gewesen sein.
Das kommt aus der git-Version von glibc. In den aktuellen Man-Pages zu glibc steht das aber auch schon drin.
Allerdings gibt es halt beim Selberbauen immer mal wieder Stress.
Eine Weile gab es Konflikte, weil Firefox auf einer über 10 Jahre alten Version von GNU autoconf bestand, die ich nicht hatte.
Dann gab es Ärger, weil Firefox H.264-Playback (Youtube und co) nur abspielen wollte, wenn man gstreamer installierte — aber es bestand auf einer 10 Jahre alten Gammelversion, die schon längst nicht mehr gewartet wurde.
Inzwischen hat Firefox auf ffmpeg umgestellt, und das tat auch eine Weile ganz gut, aber vor ein paar Tagen stellte es die Arbeit ein. Ich bin dann auf die Vorversion und zurück und habe mir das für das Wochenende vorgenommen. Jetzt ist Wochenende, und ich habe erstmal die neue glibc 2.26 installiert (die endlich reallocarray von OpenBSD übernommen hat, und einen per-Thread malloc-Cache hat, was die Performance massiv verbessern sollte in Programmen mit mehreren Threads).
Das brach mir dann erstmal alles.
Die Shell konnte plötzlich nicht mehr meinen Usernamen herausfinden, screen wurde konkreter und sagte, getpwuid könne meinen User nicht auflösen. Sowas gibt es bei glibc häufiger, weil die gerne mal Dinge als "deprecated" markieren, für die es keinen Ersatz gibt. Zum Beispiel pt_chown vor einer Weile. Das war halt unsicher, also haben sie es weggemacht. pt_chown ist das Vehikel, über das die einschlägigen libc-Routinen (grantpt) ein PTY allozieren, und es liegt in /usr/libexec, wenn es denn überhaupt liegt. Und plötzlich kam mein X nicht mehr hoch, weil mein X solange läuft, wie mein Terminal in X läuft, und das konnte kein PTY mehr allozieren.
Später hat glibc dann Sun RPC rausgeschmissen, und dann ließ sich mount(8) nicht mehr bauen. Ja super. Und jetzt haben sie nsl für obsolet erklärt und in nss ausgemistet (NIS rausgekantet und so). Gut, NIS verwende ich nicht. Als das nicht ging, habe ich halt neu gebaut, diesmal mit --enable-obsolete-nsl, aber das brauchte auch nichts. Stellt sich raus: in meine /etc/nsswitch.conf stand drin:
passwd: compatUnd das sorgte dafür, dass die glibc libnss_compat.so oder so zu laden versuchte, und das gibt es nicht mehr. Die glückbringende Änderung war dann:
shadow: compat
group: compat
passwd: filesSeufz. Bei solchen Gelegenheiten bin ich ja immer froh, dass mein getty und mein login gegen dietlibc gelinkt sind, und für Notfälle noch eine diet-shell rumliegt. So komm ich auch nach solchen glibc-Sabotageakten immer noch irgendwie ans System ran und kann Dinge fixen.
shadow: files
group: files
Aber eigentlich wollte ich ja von Firefox erzählen. Nach dem glibc-Update baut auch Firefox nicht mehr, weil glibc in sys/ucontext.h eine struct von struct ucontext zu struct ucontext_t umbenannt hat. Ich bin mir sicher, dass es da Gründe für gab, aber meine Phantasie reicht nicht, um mir welche auszudenken. Was für ein Scheiß ist DAS denn bitte?! Firefox hat einen Crash Reporter, und der will halt in diesen Strukturen herumfuhrwerken, weil man das tun muss, wenn man sehen will, in welchem Zustand das Programm beim Crashen war.
Gut, nur ein Dutzend Dateien musste ich anfassen, dann baute Firefox wieder. Aber H.264 war immer noch kaputt.
Und die Story ist richtig interessant, daher will ich sie hier mal bloggen. Wenn man so ein Problem hat, dann ist unter Linux eine der ersten und besten Debugmöglichkeiten, dass man strace benutzt. Programme unter Linux laufen im User Space, und die interagieren mit dem Kernel über Syscalls. strace fängt die Syscalls ab und gibt ihre Argumente und Ergebnisse aus. Hier ist ein Beispiel:
$ strace /opt/diet/bin/cat true.cHier sieht man ganz gut, was cat tut. execve gehört noch nicht zu dem cat selber, das ist der Aufruf von dem cat. arch_prctl ist ein Implementationsdetail, das man auf x86_64 macht, um thread local storage einzurichten (die libc weiß an der Stelle nicht, dass cat das nicht benutzt). strace auf Firefox ist natürlich viel umfangreicher, aber mit ein bisschen Geduld kann man da trotzdem sehen, was passiert, bzw. was nicht passiert. Und zwar sah ich das hier:
execve("/opt/diet/bin/cat", ["/opt/diet/bin/cat", "true.c"], [/* 60 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7fff5e690f70) = 0
open("true.c", O_RDONLY) = 3
read(3, "int main() {\n return 0;\n}\n", 65536) = 27
write(1, "int main() {\n return 0;\n}\n", 27int main() {
return 0;
}
) = 27
read(3, "", 65536) = 0
close(3) = 0
exit(0) = ?
+++ exited with 0 +++
7194 openat(AT_FDCWD, "/usr/lib64/libavcodec-ffmpeg.so.57", O_RDONLY|O_CLOEXEC) = 257 7194 --- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, si_errno=EEXIST, si_call_addr=0x7fc218252840, si_syscall=__NR_openat, si_arch=AUDIT_ARCH_X86_64} --- 7194 socketpair(AF_UNIX, SOCK_SEQPACKET, 0, [39, 40]) = 0 7194 sendmsg(36, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="\0\0\0\0\0\0\10\0\0\0\0\0\0\0\0\0", iov_len=16}, {iov_base="/usr/lib64/libavcodec-ffmpeg.so."…, iov_len=35}, {iov_base=NULL, iov_len=0}], msg_iovlen=3, msg_control=[{cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, cmsg_data=[40]}], msg_controllen=24, msg_flags=0}, MSG_NOSIGNAL <unfinished …> 7196 <… recvmsg resumed> {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="\0\0\0\0\0\0\10\0\0\0\0\0\0\0\0\0", iov_len=16}, {iov_base="/usr/lib64/libavcodec-ffmpeg.so."…, iov_len=8194}], msg_iovlen=2, msg_control=[{cmsg_len=20, cmsg_level=SOL_SOCKET,cmsg_type=SCM_RIGHTS, cmsg_data=[66]}], msg_controllen=24, msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 51 7194 <… sendmsg resumed> ) = 51 7196 openat(AT_FDCWD, "/usr/lib64/libavcodec-ffmpeg.so.57", O_RDONLY|O_NOCTTY|O_CLOEXEC <unfinished …> 7194 close(40 <unfinished …> 7196 <… openat resumed> ) = 95 7194 <… close resumed> ) = 0 7196 sendmsg(66, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="\0\0\0\0", iov_len=4}], msg_iovlen=1, msg_control=[{cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, cmsg_data=[95]}], msg_controllen=24, msg_flags=0}, MSG_NOSIGNAL <unfinished …> 7194 recvmsg(39, <unfinished …> 7196 <… sendmsg resumed> ) = 4 7194 <… recvmsg resumed> {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="\0\0\0\0", iov_len=4}], msg_iovlen=1, msg_control=[{cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, cmsg_data=[40]}], msg_controllen=24, msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 4Die Ausgabe ist ein bisschen verwirrend mit dem unfinished und resumed; das kommt daher, dass Firefox mehr als einen Prozess/Thread aufmacht, und die miteinander reden, und ich alle von denen auf einmal beobachte. Aber mal grob: Prozess A ruft openat(AT_FDCWD,…) auf, das ist äquivalent zu open(…). Kriegt als Ergebnis 257 und direkt ein Signal SIGSYS mit si_code SYS_SECCOMP.
Das ist ziemlich coole Scheiße, weil das genau das ist, was ich auf dem 32c3 in meinem Vortrag "Check your privileges" als Broker-Architektur vorgestellt hatte. Die haben das aber nicht über Überladen von openat gemacht, sondern die haben das so gemacht, dass SECCOMP nicht den Prozess abbricht sondern dieses Signal schickt. Das Signal fangen sie dann ab, und der Handler von dem Signal kann dann nachvollziehen, was der Code zu tun versucht hatte, in diesem Fall openat, und das anders lösen — nämlich über sendmsg an den Broker, wie wir in dem strace schön sehen können. Der Broker macht recvmsg, kriegt die Anfrage (und wir sehen in den Daten von dem sendmsg auch schön den Dateinamen), und öffnet die dann. Die Zahl am Anfang ist übrigens die PID bzw. Thread-ID.
Der Broker macht dann selber nochmal openat, hat keinen SECCOMP-Filter installiert, das openat läuft durch, und dann schickt der Broker mit dem zweiten sendmsg oben den Deskriptor 95 zurück an den anfragenden Prozess in der Sandbox. Der kriegt das dann in dem recvmsg als Deskriptor 40 reingereicht (Deskriptoren sind immer relativ zum Prozess, daher findet hier im Kernel eine Übersetzung statt). Das ist mal echt coole Scheiße, und ich bin einigermaßen schockiert, dass Firefox so Bleeding-Edge-Kram überhaupt implementiert hat. Sehr cool!
Warum ich das hier alles erwähne: weil das bei mir auf die Nase fiel, denn zum Abspielen von H.264 lädt Firefox libavcodec von ffmpeg, und ffmpeg ist gegen einen Haufen Codecs und Libraries gelinkt, und die liegen bei mir eben nicht alle in /usr/lib64, sondern beispielsweise liegt libva in /usr/X11R7/lib64 (libva macht hardware-assistierte Dekodierung und Anzeige von u.a. H.264-Videos). Und es stellt sich raus, dass Firefox in dem Broker eine Liste von erlaubten Verzeichnissen hat, aus denen Dateien geöffnet werden können, und die wird nicht aus /etc/ld.so.conf oder so generiert, sondern die ist hardkodiert im Quellcode. Falls jemand mal selbst gucken will: Das ist in security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp (nach policy->AddDir gucken).
Nachdem ich da meine Pfade eingetragen habe, kann der Firefox ffmpeg laden — aber das Video immer noch nicht spielen. Das sei jetzt korrupt, sagt er. Ist es natürlich nicht. Mist. Na mal schauen. Fortschritt ist immer nur ein Schritt zur Zeit.
Update: Ein Einsender zur glibc-Situation:
Das Update habe ich für unsere cross-gebauten Umgebungen diese Woche auch gemacht. Mal so ein paar Sachen, die aufgefallen sind:
- gcc 5.4 baut dann nicht mehr (ucontext_t, auch ein paar Stack-Sachen), betrifft auch neuere Versionen von gcc
- iproute2 4.11 baut auch nicht mehr (4.12 schon, also mir egal). IIRC haben sie irgendwo vergessen stdint.h für UINT16_MAX einzubinden, und das muss vorher zufällig über irgendeinen anderen Header mit reingekommen sein (also ganz klar nicht die Schuld von glibc)
- gdb 8.0 baut nicht mehr auf x64 (ARM geht), irgendwas mit putc(), das nur ein Argument bekommt oder so.
Ganz großes Kino.
In der Tat. o_O
Bonus: Einziger Betroffener ist systemd, der Bug wurde von Lennart Pöttering aufgemacht.
udp.c in the Linux kernel before 4.5 allows remote attackers to execute arbitrary code via UDP traffic that triggers an unsafe second checksum calculation during execution of a recv system call with the MSG_PEEK flag.
Das klingt jetzt schlimmer als es ist, denn MSG_PEEK wird selten benutzt, UDP wird selten benutzt, und MSG_PEEK bei UDP wird noch seltener benutzt. Mir ist persönlich kein Fall bekannt.Ich habe dieses Flag in meiner Laufbahn 1-2 Mal benutzt. Man nimmt es, wenn man von einem Socket ein paar Bytes lesen will, ohne die zu lesen (d.h. wenn man danach nochmal read aufruft, kriegt man die Bytes wieder). Ich benutze das in gatling, um zu sehen, ob Daten auf einer SSL-Verbindung wirklich wie ein SSL-Handshake aussehen — allerdings auf einem TCP-Socket.
Kurz gesagt: Ich mache mir da jetzt keine großen Sorgen. Die DNS-Implementationen von glibc, dietlibc und djb benutzen jedenfalls nicht MSG_PEEK, und wenn es um UDP geht, ist DNS der übliche Verdächtige.
Ich will das nicht kleinreden, das ist ein ganz übler Bug und die sollten sich was schämen. Aber es ist kein "OMG die NSA hat mich gehackt, ich reinstalliere und mache alle Keys neu"-Bug. Außer ihr fahrt auf eurem Server irgendwelche UDP-basierten Protokolle.
Update: Im OpenVPN-Code findet grep auch kein MSG_PEEK. VPN und VoIP wären die anderen in Frage kommenden Codebasen.
Update: Ein paar Leser haben bei Debian und Gentoo mal ein bisschen rumgesucht und fanden als potentiell verwundbare Pakete dnsmasq und VNC-Implementationen. dnsmasq läuft praktische allen Plasteroutern. Das wäre in der Tat potentiell katastrophal.
Update: Einsender weisen darauf hin, dass systemd angeblich MSG_PEEK benutzt, und dass möglicherweise noch Chrome in Frage kommt als Angriffspunkt, wegen QUIC.
getrandom ist ein neuer Syscall, der einem Zufall liefert, als würde man aus /dev/urandom lesen. getentropy ist ein Wrapper um getrandom, der, wenn der Syscall nicht verfügbar ist, halt /dev/urandom öffnet und daraus liest.
In neuen Code solltet ihr getentropy verwenden. Das API kommt von OpenBSD.
Wieso getentropy nehmen und nicht aus /dev/urandom lesen? Weil getentropy, wenn der Syscall verfügbar ist, nicht /dev/urandom öffnen muss, d.h. das Programm funktioniert auch in einem chroot-Jail, in dem der Admin /dev/urandom anzulegen vergessen hat.
Das OpenBSD-API sagt, dass getentropy höchstens 256 Bytes auf einmal liefern kann. Den getrandom-Syscall gibt es seit Linux 3.17.
Ich hatte Support für getrandom und getentropy in der dietlibc, seit ich von dem API gehört hatte, aber ich hatte die Prototypen nach unistd.h getan. glibc tut es nach sys/random.h. Ich habe dietlibc also jetzt geändert, auch sys/random.h zu benutzen.
Update: Oh und noch eine gute Nachricht: Wenn ihr openssh mit der neuen glibc übersetzt, sollte das direkt tun, denn ich habe vor einer Weile bei denen einen Bug gefiled, dass der getrandom-Syscall in ihrer Sandbox freigeschaltet werden muss. Haben sie sofort getan. Müsste also alles out of the box funktionieren.
Update: Es gibt noch andere Gründe für getentropy. Aus der Manpage zu getrandom:
If the urandom source has not yet been initialized, then getrandom()Das umgeht elegant ein wichtiges Sicherheitsproblem, wenn Geräte zum Bootzeitpunkt noch nicht genug Entropie gesammelt haben.
will block, unless GRND_NONBLOCK is specified in flags.
Update: Den Fallback-Code scheint es in glibc nicht zu geben. Ich nahm an, dass die den haben werden, weil glibc sonst für jeden Scheiß einen Fallback-Code hat, und ich natürlich in dietlibc auch einen Fallback implementiert habe.
Erstmal gibt es von RPM direkt zwei Stränge, RPM 4 und RPM 5, und beide behaupten von sich, das "offizielle" RPM zu sein. Lolwut?
Gut, hole ich mir RPM 4, weil das auf der Zieldistro eingesetzt wird. Kompiliert nicht. Will NSPR und NSS haben, aber guckt nicht in den Default-Install-Pfaden, konsultiert nicht nspr-config, was genau dafür da ist, und fragt nicht pkg-config, was auch genau dafür da ist. Nein. Und findet es dann natürlich nicht.
OK, überschreibe ich $CC, findet er das. Und findet dann Berkeley DB nicht. Berkeley DB? Srsly? Ist systemweit installiert. Klar, könnte man nehmen. Will man aber nicht. Nein, RPM will die Sourcen in seinem Quell-Tree haben und dann neu bauen. Gut, lege ich ihm das hin.
Stellt sich raus: Einfach ein RPM zu den anderen RPMs auf dem Install-ISO tun reicht nicht. Nein. Man muss da auch "repodata" neu erzeugen. Und das kann RPM nicht.
Natürlich nicht! Warum auch?
Dafür braucht man ein externes Tool namens createrepo. Das ist in Python.
Da gehen ja bei mir direkt alle Warnlampen an.
Installiere ich das also. Rufe es auf. Geht nicht. Braucht yum. Auch ein Python-Tool. Die zweite Riege Warnlampen geht an. Installiere ich also yum. Rufe es auf. Geht nicht. Braucht das rpm-Modul. Wie, ist das nicht bei RPM dabei?
Doch, ist es. Aber RPM installiert das nicht, außer man bittet explizit darum.
Also nochmal RPM gebaut. Rufe yum auf. Geht nicht. "No module named sqlitecachec". Oh super! Wo kriegt man das her? Steht da nicht. Warum würde das da auch irgendwo stehen? Ist ja nicht so, als wäre das hier alles die zentrale Software, auf der eine "Enterprise"-Distribution aufsetzt! Warum sollte das einfach so funktionieren?
Googelt man das also, findet keine Homepage. Nee, warum auch. Man findet diverse Distro-Pakete. Oh, das kommt von "yum-metadata-parser"! Na da hätte man ja auch direkt selber drauf kommen können!1!! 2010 zuletzt angefasst.
yum geht. createrepo immer noch nicht. Braucht "deltarpm". Sagt mal, soll das hier ein Scherz sein? Sogar der Rotz von Gnome prüft vor dem Bauen, ob die Abhängigkeiten erfüllt sind!
Oh und wer hat sich denn bitte das RPM-Dateiformat ausgedacht?! Was soll das werden? Soll das außerirdische Invasoren in den Wahnsinn treiben, damit sie nicht die Menschheit ausrotten können? Heilige Scheiße. Dagegen ist ja der Debian-Kram einleuchtend und selbsterklärend! Und selbstdokumentierend gar. Wie ein .deb funktioniert, kann man mit file und Hausmitteln rausfinden, man braucht nicht mal einen Hex-Editor.
Meine Güte. Und ich kenne ansonsten durchaus zurechnungsfähige Leute, die schwören auf CentOS!
Update: Vielleicht sollte ich mal sagen, wie ich mir so ein Paketverwaltungstool vorstelle. Meine Anforderung wäre, dass das immer funktioniert. Python zerschossen? Perl nicht installiert? glibc-Update in der Mitte fehlgeschlagen? Der Paketmanager muss noch gehen und das noch retten können. Und er muss klein genug sein, um mit Metadaten in 5 MB zu passen. Muss auch ohne OpenSSL und curl und wget arbeiten können, zumindest im Notfall. Von den Systemen, die ich bisher gesehen habe, gefällt mir pacman (Arch-Linux) am besten. Aber da geht noch was.
Update: Erwähnte ich, dass repodata dann XML und Sqlite ist? Geht's noch? Wieso habe ich dann Berkeley DB in RPM reinlinken müssen? Oh und wenn mir der Distro-Installer während der Installation Werbung anzeigt, habe ich da überhaupt kein Verständnis für.
Update: Wobei mir die Übung ja wenigsten eine Sache klar gemacht hat. Jetzt verstehe ich, wieso überall diese Cloud-Spezial-Distros aus dem Boden sprießen. Das "Minimal"-Redhat ist 1GB groß nur für Code. Und da ist dann kein netstat bei.
Wobei ich auch sagen muss, dass es sich hier um eine GNU-Extension in einer selten verwendeten Funktion handelt, und die ist obskur genug, dass ich noch nie von ihr gehört habe. Wenn ich schätzen müsste, wer sowas anwendet, würde ich vielleicht auf Bash tippen.
Update: Github findet fünfstellig Hits, und ein Leser schreibt, systemd benutzt das.
Eine wichtige Sicherheitstechnologie ist, dass man dafür sorgt, dass die Shared Libraries nicht immer alle an der selben Adresse geladen werden. Es geht darum, Angreifern das Ausnutzen von Sicherheitslücken zu erschweren. Dieses Verfahren heißt ASLR.
Unter Linux ist das umgesetzt, indem mmap die Speicherbereiche randomisiert, d.h. ld.so muss gar nicht viel dafür machen. Jetzt gibt es zwei Probleme. Erstens ist ld.so ein Executable, keine Shared Library, d.h. es wird nicht an eine zufällige Adresse geladen, sondern an eine statische. Zweitens ist das Hauptprogramm ein Executable, keine Shared Library. Der ld.so von glibc ist deshalb jetzt doch eine Shared Library, um dieses Einfallstor zu schließen. Aber das Hauptprogramm landet doch immer noch immer an der selben Adresse, außer — ja außer, man kompiliert es mit -pie. Im Grunde gibt es gar keinen echten Unterschied zwischen Hauptprogramm und Shared Library in ELF, außer dass das Hauptprogramm im Header eine Adresse stehen hat, an die es geladen werden will, und die Shared Library kann irgendwohin geladen werden. Wenn man also ASLR in allen Komponenten haben will, dann müssen alle Komponenten ELF-technisch Shared Libraries werden.
So, was ist jetzt mit dietlibc. dietlibc erzeugt statische Binaries, d.h. ohne ld.so und ohne libc.so. Das ist ja gerade die Idee bei der dietlibc. Ich fragte mich jetzt, ob ich es nicht hinkriegen kann, ein statisches Binary zu erzeugen, dass der Kernel hinladen kann, wo er will. Erstmal geht es mir nur um meine Entwicklungsplattform, AMD64. Dort und bei x86 insgesamt ist es so, dass ein Funktionsaufruf zu "call 1234" wird, aber 1234 ist nicht die absolute Adresse, an die man hinspringen will, sondern relativ zur aktuellen Position des Instruction Pointer. Wenn die Calls alle relativ innerhalb des Code-Moduls sind, dann ist es auch egal, an welcher Stelle im Speicher das liegt.
Speicherzugriffe auf Variablen laufen bei x86 aber über absolute Adressen. Auf dem 32-bit x86 hat man dann ein Problem, denn man kann keine Adressen in Relation zum aktuellen Instruction Pointer konstruieren. Bei AMD64 ist das anders, daher dachte ich, wenn ich mich erstmal darauf konzentrieren, dann ist das ein Selbstläufer. Ist es leider nicht.
Das Problem ist, dass ich anscheinend der erste bin, der das machen will. Alle anderen wollen normale dynamische Binaries erzeugen, und da laufen alle Zugriffe über die Glue-Datenstrukturen. Mein erstes Problem ist, gcc zu sagen, dass er diese Datenstrukturen bitte nicht benutzen soll. Mein aktueller Ansatz dafür ist -fvisibility=hidden, aber das hilft leider nur ein bisschen. Für in Headern als "extern" deklarierte Symbole hilft es nicht.
Gut, dachte ich mir laut seufzend. Dann muss mein Startup-Code halt ran. Ich kompiliere also meinen Code mit -fpic -fvisibility=hidden und linke daraus erfolgreich eine Shared Library. Wenn ich die aufrufe, wird mein Startup-Code ausgeführt. GOT und PLT sind eh schon in den Speicher gemappt (das sind diese Glue-Datenstrukturen). Da stehen nur noch falsche Dinge drin. So schwer kann das ja wohl nicht sein. Ich fange also an, da ein bisschen Code zu schreiben, und scheitere gerade an trivial anmutenden Details.
Und zwar habe ich in meiner "shared library" ja keinen ELF Interpreter ("ld.so") drin. Woher weiß denn mein Code, wo die GOT ist? Das steht in ELF-Datenstrukturen, die Teil der Shared Library sind. Die mappt der Kernel in den Speicher. Ich weiß nur nicht, wo die genau sind. Der Mechanismus dafür heißt AUXVEC. In C ist das Hauptprogramm ja die Funktion main(), und die kriegt die Kommandozeilenargument und das Environment übergeben. Das Environment ist ein Array von char*, und NULL markiert das Ende der Liste. AUXVEC ist einfach dahinter im Speicher. Wer das mal sehen will:
% LD_SHOW_AUXV=1 /usr/bin/trueDa steht bei mir sowas hier:
AT_SYSINFO_EHDR: 0x7ffc0ab78000 AT_HWCAP: bfebfbff AT_PAGESZ: 4096 AT_CLKTCK: 100 AT_PHDR: 0x400040 AT_PHENT: 56 AT_PHNUM: 8 AT_BASE: 0x7fa6e6d65000 AT_FLAGS: 0x0 AT_ENTRY: 0x401460 AT_UID: 1000 AT_EUID: 1000 AT_GID: 100 AT_EGID: 100 AT_SECURE: 0 AT_RANDOM: 0x7ffc0aadb909 AT_EXECFN: /usr/bin/true AT_PLATFORM: x86_64Hier kann man ganz schön sehen, dass AT_PHDR (ein Zeiger auf den ELF Program Header) nicht an ASLR teilgenommen hat, während AT_BASE (die Basisadresse von ld.so) randomisiert wurde. AT_ENTRY ist die Adresse im Speicher, bei dem die Ausführung beginnt. Das ist nicht main() sondern ein Symbol namens "_start", in dem die libc ihren Kram macht und dann main() aufruft.
Gut, dachte ich mir, das ist ja weitgehend was ich haben will. Alles super. Von AT_BASE aus kann ich mich durch die ELF-Strukturen hangeln (und die Werte darin sind alle relativ zur Ladeadresse des Binaries). Da finde ich die GOT und kann mal schauen, was da so drin steht, und was ich relozieren muss. Viel mehr als AT_BASE draufaddieren wird das nicht sein, wenn ich Glück habe.
Und was stelle ich jetzt fest? AT_BASE wird nicht übergeben. AT_BASE ist nämlich die Ladeadresse des ELF Interpreters, also ld.so, und den habe ich ja nicht. Meine eigene Base-Adresse sagt mir der Kernel nicht. Immerhin sagt mir der Kernel immer noch AT_PHDR, und das ist auch schon mal was, aber von da aus finde ich die Basis-Adresse nicht, zu der mein Binary geladen wurde. Also jedenfalls nicht offiziell. In der Praxis runde ich von da auf den Page-Anfang ab und hab es, aber das ist ja iih-bäh. Ich fürchte, ich bin schlicht der Erste, der gerne statische PIE-Binaries haben will.
Seufz.
In den gemappten Datenstrukturen stehen leider überall nur Angaben relativ zum Beginn der Datei drin. Und wo das Mapping losgeht, sagt mir der Kernel nicht.
Sachdienliche Hinweise werden dankend entgegen genommen!
Infrastruktur wird überhaupt nur beurteilt, wenn sie versagt - ansonsten wird sie gar nicht wahrgenommen. Der Linux-Kernel wird an "ReiserFS/btrfs/XFS/ext4 hat meine Daten gefressen" und an "Der Kernel cored, wenn die Kiste aus dem Sleep aufwacht" gemessen und nicht an "Oh, der write(2) System Call hat meine Daten tatsächlich auf die Platte geschrieben.Ich stimme dem im Detail nicht zu (ich bewerte den Kernel auch nach neuen Features und Performancegewinnen bei alten Features), aber das kann man auch durch geschickte Wortauslegung wegdefinieren. Die Ausführungen finde ich trotzdem weiterhelfend und konnte das so auch schon beobachten. Bei so langen Statements erwähne ich häufig den Einsender nicht, aber in diesem Fall linke ich mal auf ein vergleichbares Statement von ihm, denn Kris macht das seit vielen, vielen Jahren und weiß, wovon er spricht. Das Verständnis der verschiedenen Herangehensweisen von Infrastruktur-Kram und Feature-Kram ist wichtig und es kann uns allen nur hilfreich sein, wenn wir es uns immer mal wieder vor Augen führen.Das ist ein großer Unterschied zu Libreoffice, das keine Infrastruktur ist und an neuen Best Cases (= neue Features) statt an seinen Worst Cases gemessen zu werden.
Infrastrukturentwickler verwenden also dieselben Sprachen, Tools, Unit-Tests, VCVSe und dergleichen mehr, arbeiten aber unter einem komplett anderen Wertesystem.
Wenn da einer auf der LKML ankommt und zeigt "Hier, neuer shiny code" (= neuer Best Case), dann wissen die schon, daß der aus einem anderen, falschen Universum ist. Infrastrukturentwickler wissen, daß Code auch immer Bugs/LoC hat, also eine Liability und kein Asset ist. Das kann man natürlich versuchen, solchen Personen immer wieder, und wieder, und wieder zu erklären, aber das kostet mehr Energie als es wert ist.
Man will in einer Infrastruktur auch nicht mehr Leute haben. Wenn wenn man da einmal genug Teilnehmer hat, daß das Projekt läuft, dann sind mehr Leute auch kein Asset, sondern eine Liability - und das gilt nicht nur für die LKML-Regulars oder Kernel-Committer, oder glibc-Developer, sondern auch für Leute, die Deine Gasleitungen oder Stromkabel warten, und sogar für Wikipedia Admins (sic!). Das ist ein ganz besonderer Menschenschlag, mit einer besonderen Arbeitsethik, die vollständig daraus stammt, daß die ihre Arbeit dann gut machen, wenn sie ihnen nicht gedankt wird und sie einfach niemand bemerkt.
Infrastrukturprojekte wollen nicht nur Deinen Code nicht, sondern auch Deine Mitarbeit nicht, weil sie - ausreichend Basis-Masse vorausgesetzt - beides nicht brauchen. Insbesondere können sie Dich nicht brauchen, wenn Du nicht diesen Infrastruktur-Mindset hast, weil Du sonst auch die Kritik und die Arbeitssituation unter einer Fail-Metrik wie sie bei Infrastuktur herrscht nicht aushältst.
Wenn man da als shiny bling bling Feature-Developer hin kommt, oder als eine 'Findet mich und meine Anliegen wichtig'-SJW-Mentalität, dann passiert eine Sarah Sharp.
Das Problem war, dass dietlibc einige OpenBSD-APIs implementiert, konkret arc4random und getentropy, genau damit OpenSSH das verwenden kann und in den Genuss des neuen getrandom-Syscalls unter Linux kommt. Das configure-Skript erkennt das auch und benutzt dann meine dietlibc-Implementationen. Die glibc hat diese APIs nicht. Der OpenSSH-Server hat einen "sandbox"-Modus für Privilege Separation, in dem er SECCOMP-Filter benutzt, um die erlaubten Syscalls zu filtern. Und dort war nun ausgerechnet der getrandom-Syscall noch nicht freigegeben, weil der Code für den glibc-Port das nicht benutzte.
Manchmal kann es eben auch Nachteile haben, wenn man Leuten zu sehr entgegen kommt :-)
Geschichten aus dem Linux-Schützengraben.
Naja, kein Problem, dachte ich mir, für sowas gibt es ja eine Rescue-Umgebung. Wollte die klicken — der Knopf fehlte im Webinterface. Nanu? Ticket aufgemacht. Knopf da. Knopf gedrückt — Server platt, kein Rescue kommt hoch. Ticket aufgemacht. Pause. Antwort: Wir haben da mal ne Intel e100 NIC eingebaut, damit das Rescue hochkommt, und die Onboard-NIC ausgemacht. Musst du dann in deinem Debian fixen.
Die Meldung sah ich aber erst, nachdem ich mein Problem gefixt hatte, und umgebootet hatte, und da kam dann das System zwar hoch, aber ohne Netzwerk.
Also zurück ins Rescue, aber das kam dann auch nicht mehr hoch. Ticket aufgemacht. Die haben irgendwas gebastelt, damit kam das wieder hoch.
Oh, erwähnte ich, dass das Rescue-System ohne mdadm kam? Ich hab seit gefühlt 10 Jahren kein Rescue-System mehr gebraucht. Stellt sich raus, dass sowas heutzutage ein richtiges Debian ist, wo man konfigurieren kann, inklusive apt-get install mdadm. Allerdings will er dafür über 50 MB nachladen, also hab ich lieber ein statisches dietlibc-mdadm hochgeladen und benutzt.
Das Problem, so stellte sich raus, war: Die e100-Netzwerkkarte möchte gerne Firmware nachladen, die nicht da war. Ich trag also das Repository ein und rufe apt-get auf … da baut irgendein Debian-Automatismus eine initrd neu und die ist nicht mehr bootfähig.
Nun hat mir der Hoster freundlicherweise ein KVM zur Verfügung gestellt, sonst hätte ich den Aspekt gar nicht sehen können. Aber mit der neuen initrd fand der Kernel sein root-fd nicht und panicte.
Die nächsten Schritte waren: Debian dist-upgrade (das war noch ein squeeze), grub erneuern, grub stirbt mit symbol grub_divmod64_full not found. Bekanntes Problem, offenbar, aber die Lösungen funktionierten alle nicht. purge, reinstall, deinstall, config fummel, reinstall, auf die zweite platte auch installieren, half alles nichts.
In der Mitte hatte ich noch versucht, einen eigenen Kernel zu bauen, der auf der Hardware laufen würde, aber der tat es nicht und auf dem 80x25-Screen der KVM sah ich dann auch nur die letzten statischen Zeilen eines generischen Panic-Stackdumps und konnte nicht hochscrollen. Aus irgendeinem Grund kamen Tastatureingaben nicht an über die KVM. Ich tippe auf ein Java-Problem, aber am Ende — who cares.
Der einzige Ausweg war am Ende, dass ich einen eigenen grub 2.02 beta 2 aus den Sourcen kompiliert habe. Der warf zwar auch noch lustige Fehlermeldungen, aber hat dann am Ende den Kernel doch gebootet.
So, jetzt hab ich zwar immer noch einen veralteten gcc und eine veraltete glibc, aber immerhin ist die Kiste wieder oben. Seufz.
Update: Warum war da so ein antikes Debian? Weil alle von außen erreichbaren Dienste eh von mir und nicht von Debian sind.
Update: Es gibt einen xkcd dazu! Großartig, den kannte ich noch gar nicht :-)
Update: Das SSL-Zertifikat ist dann auch mal neu, wegen des OpenSSL-Bugs gerade.
CVE-2013-4458 Stack overflow in getaddrinfo with large number of results for AF_INET6 has been fixed
Das ist remote code execution via unvertrauenswürdiger, unverschlüsselter Daten aus dem Internet. GANZ gruselig. Man würde denken, die libc ist der stabilste, sicherste Teil des Codes. Der ordentlich auditierte Code. Aber offensichtlich nicht. Auch die anderen Bugs sind gruselig, aber für die muss man schon auf dem System sein, um die ausnutzen zu können. Mein Lieblingsbug ist der hier:CVE-2013-4788 The pointer guard used for pointer mangling was not initialized for static applications resulting in the security feature being disabled.
Das ist ein typischer Ulrich Drepper. Als der noch glibc-Maintainer war, fand er, dass statisches Linken alt und scheiße ist und nicht mehr unterstützt gehört. Als er dann "Pointer mangling" einbaute, hat er es daher nur für dynamische Programme getan. Denn hey, wer linkt heutzutage noch statisch!1!!
Bei dieser Pointer-Verschlüsselung handelt es sich um eine Buffer Overflow Mitigation, also eine Maßnahme, die im Falle von Buffer Overflows die Ausnutzung erschweren soll. Die traditionalle Methode sind Stack Canaries, das sind "zufällige" Werte auf dem Stack. Und bevor man potentiell im Rahmen eines Buffer Overflows überschriebene Werte übernimmt, prüft man, ob der Kanarienvogel noch lebt, wie damals in den Minen. In der glibc wurde ein anderer Weg gewählt, nämlich dass die Pointer "verschlüsselt" werden. Dafür wird statt des tatsächlichen Wertes ein XOR mit einem "zufälligen" Wert und eine Rotation gemacht. Die Idee ist, dass der Angreifer das dann zwar überschrieben kann, aber da er den XOR-Wert nicht kennt, kann er nicht wissen, welchen Wert er da hinschreiben muss. Und der Wert ändert sich mit jedem Programmaufruf. Außer halt wenn man glibc nimmt und statisch linkt.
* CVE-2013-0242 Buffer overrun in regexp matcher has been fixed (Bugzilla #15078). * CVE-2013-1914 Stack overflow in getaddrinfo with many results has been fixed (Bugzilla #15330).Also habe ich mir gedachte, hey, die installiere ich doch mal, die Version ohne den so gut wie alle Netzwerk-Anwendungen betreffenden Remote Exploit. Aber in make install begibt sich das Makefile in eine Endlosschleife. m(
Diese Art von Bug würde man vielleicht von Apple erwarten. Ernsthaft, glibc?
#define MONE -1.0 /* -1 */(Danke, Andreas)
-#define TWO -2.0 /* -2 */
+#define TWO 2.0 /* 2 */
#define TWO5 0x1.0p5 /* 2^5 */
$ for i in `seq 1 1000`; do mysql -u root --password=bad -h 127.0.0.1 2>/dev/null; doneOH DIE SCHMERZEN!
mysql>
Update: Weil vereinzelt Leuten gcc oder der glibc die Schuld geben wollen: Blödsinn. Niemand außer MySQL ist Schuld. Der Standard sagt mehr als deutlich, dass memcmp int zurückliefert, nicht char, und dass der Wert positiv, negativ oder 0 sein kann. Nirgendwo steht, dass es 1 oder -1 ist. Da kann sich MySQL nicht rausreden.
Seit dem gibt es in Linux einige mehr oder weniger geniale Änderungen, die wir nie unterstützt haben, weil Multithreading nie so richtig wichtig war für die dietlibc. Mir schonmal eh nicht so, aber auch die Zielplattformen haben normalerweise nie mehr als einen Core gehabt. Das hat sich inzwischen geändert.
Die erste wichtige Neuerung ist Thread Local Storage. Das funktioniert so, dass man in den Threads auf x86 ein Segment-Register so belegt, dass man über das Segment ab Offset 0 auf den Thread-Parameter-Block zugreifen kann. Es ist auch vorgesehen, dass man selbst in seinem Code Variablen thread local deklarieren kann, aber den Sinn davon habe ich nie gesehen, die kann man ja auch gleich auf den Stack tun oder vom Heap allozieren, wenn sie größer sind. Der Punkt ist jedenfalls, dass alle möglichen Thread-Funktionen wissen müssen, welcher Thread sie gerade aufruft, z.B. können Mutexe rekursives Locking erlauben und müssen daher wissen, ob dieser Lockversuch vom selben Thread kam wie der ursprüngliche. Kurz gesagt: bei Threading-Code war es immer ein Flaschenhals, wenn der Code gucken musste, welcher Thread das eigentlich gerade ist. Die Alternativen sind alle doof; man könnte einen Syscall aufrufen, um die PID oder TID zu kriegen, oder man könnte den Stack Pointer nehmen und in einer globalen Datenstruktur nachgucken (allerdings muss die dafür gelockt werden und wie gesagt müssen Mutexe auch die Thread-ID wissen). Mit Thread Local Storage ist das nur noch ein einziger Speicherzugriff über das Spezial-Segment. Das war also schonmal gut, aber ich hab immer das Gefühl gehabt, ich müsste jetzt auch Futex-Support implementieren, um überhaupt in einer Liga wie NPTL spielen zu können. Das habe ich heute mal gemacht, und die Ergebnisse sind ernüchternd.
Der alte Code in dietlibc benutzt im Wesentlichen Spinlocks, d.h. eine Schleife, die immer wieder versucht, den Lock zu kriegen, bis es halt klappt. Damit dabei nicht die CPU so doll belastet wird, sagt er zwischen den Iterationen dem OS, dass mal ein anderer Thread laufen soll jetzt. Sieht sehr krude und amateurhaft aus, fand ich immer.
Futex, zum Vergleich, ist ein geradezu geniales Verfahren. Man nimmt sich dafür den Lock, also eine Speicherstelle, und zählt den mit einer atomaren Operation von 0 auf 1 hoch. Wenn nach dem Hochzählen 1 drin steht, dann weiß man, dass man den Lock hat und sonst niemand. Wenn dann da 2 oder so drinsteht, dann hat man den Lock nicht, und benutzt den futex-Syscall, um dem Kernel zu sagen, dass man auf diesen Lock hier warten will. Der Kernel suspendiert dann den Thread.
Beim Unlock geht man analog vor; man zieht atomar einen ab, und wenn man bei 0 rauskommt, ist alles gut und man ist fertig, ansonsten ruft man den Futex-Syscall auf und sagt an, dass man mal einen der wartenden Threads aufgeweckt haben will. Es gibt da noch zwei-drei Komplikationen aber im Wesentlichen ist es das. Den Syscall benutzt man nur, wenn man nicht im Userspace über die atomaren Operationen schon alles geklärt hat.
glibc hat über NPTL seit vielen Jahren Futex-Support.
Mein Test-Programm ist untypisch, denn es macht vier Threads auf, die alle dauernd um den selben Lock konkurrieren. An sich sind Locks ja genau auf den anderen Fall optimiert, nämlich dass es keinen Wettbewerb gibt im Normalfall, insofern ist das kein sonderlich guter Test, aber es ging ja ursprünglich auch gar nicht um den Durchsatz, sondern die Threads prüfen auch, ob sie tatsächlich als einzige Zugriff hatten.
Hier ist der Durchsatz (am Anfang insgesamt und die vier Zahlen am Ende sind für die einzelnen Threads):
glibc:Die Ergebnisse haben mich ja nun doch massiv überrascht. Falls sich jemand den Code mal angucken will: hier ist er. Ich habe den neuen Futex-basierten Locking-Code dann lieber doch nicht eingecheckt angesichts dieser Zahlen :-)
5840543 iterations: 1450529 1436218 1478655 1475141.dietlibc alt:
80426491 iterations: 18957811 16267831 19589958 25610891.dietlibc neu mit futex:
11477382 iterations: 2868532 2830930 2876189 2901731.
Oh, einen noch. Es gibt da noch eine Optimierung, die man gerne macht beim Locking. Man ruft nicht sofort den Kernel an, sondern probiert es erst ein paar Mal. Der Gedanke dahinter ist, dass normalerweise so ein Lock ja nur sehr kurz gehalten wird, und man sich den Syscall-Overhead auch sparen kann, wenn der, der den Lock gerade hält, nur mal kurz ein-zwei Variablen schreibt und ihn dann wieder freigibt. Das hat mein Futex-Code natürlich auch getan. Und zwar erst 100 Mal, dann nur noch 10 Mal. Hat den Durchsatz signifikant erhöht, das auf 10 zu senken. Das hat mich auch überrascht.
Update: Falls ihr mal eure libc testen wollt: das lief auf einem 64-bit Linux auf einem Phenom 1090T (3.2 GHz, 6 cores).
Update: Die glibc-Patches, die das memcpy rückwärts kopieren lassen, kommen übrigens von Intel, weil sie auf dem Atom die Prefetch-Logik verkackt haben.
Was soll ich sagen. Kein Arbeitgeber passt so perfekt zu Ulrich wie Goldman Sachs. (Danke, Hannes)
So hatte ich vor einer Weile das Problem, dass auf meinem Notebook mein WLAN nicht mehr funktionierte. Das lag daran, dass der Intel-WLAN-Treiber anfing, eine Firmware hochzuladen, und dafür das Hotplug-Interface zu benutzen. Hotplug, das ist eine archaische Shellskript-Sammlung von Greg Kroah-Hartmann, in einem Shellskript /sbin/hotplug gipfelnd, das dann andere Shellskripte aus /etc/hotplug/* aufruft, und dort die eigentlichen Daten über magische Environment-Variablen übergibt. Nach einer Weile rumdebuggen habe ich damals rausgefunden, dass das Hotplug sich aus den magischen Environment-Variablen (der ganze Prozess ist m.W. nicht brauchbar dokumentiert) einen Pfad zusammenpopelt, und dann am Ende cp aufruft, um /lib/firmware/radeon/RV770_me.bin nach /sys/devices/platform/r600_cp.0/data zu kopieren. Welches cp das am Ende wird, das hängt von $PATH ab. Ich habe ein /usr/bin/cp, ein /bin/cp und ein /opt/diet/bin/cp, alle unterschiedlich. Mein /usr/bin/cp ist von GNU coreutils, und das meinte der Autor von hotplug wohl. /bin/cp ist ein Notfall-cp aus einer alten Version von embutils, falls ich /usr nicht mounten kann oder mir die glibc zerschossen habe. /usr/bin ist vor /bin im Pfad, daher betrifft das nie jemanden. /opt/diet/bin/cp ist das aktuelle cp aus embutils, das ist für meinen User noch vor /usr/bin im Pfad. Hotplug wird aber direkt vom Kernel aufgerufen und erbt daher das Environment von init. Und das hat das Default-Environment, das der Kernel setzt beim Aufruf von init, nämlich
PATH=/sbin:/bin:/usr/sbin:/usr/binAm Ende hat hotplug also das alte Notfall-cp aufgerufen, das in Blöcken zu 1024 Bytes kopiert. Versteht mich nicht falsch: alle dieser cp Binaries sind willens und in der Lage, eine Datei zu kopieren, und hotplug benutzt auch keine komischen GNU-only Flags. Aber das Firmware-Interface des Kernels hat damals Dinge angenommen, die nur GNU cp so gemacht hat. Ich habe die genauen Details vergessen, aber das Notfall-cp hat 1024-Bytes geschrieben, und der Kernel nahm an, dass die Firmware in nur einem Chunk geschrieben wird, und daher schlug das fehl.
Nachdem ich meine Fassungslosigkeit überwunden habe, habe ich damals /etc/hotplug/firmware.agent angefasst, damit er händisch /usr/bin/cp aufruft und nicht bloß cp über $PATH, und damit war das Problem gelöst.
Gestern habe ich jetzt anlässlich dieser Meldung hier den Kernel 2.6.32-rc6 auf meinem Desktop probiert. Mein Desktop hat kein WLAN und lädt an sich keine Firmware. Auf meinem Desktop ist auch seit irgendwann mal vorbeugend /bin/cp ein Symlink auf /usr/bin/cp. Um so größer war meine Überraschung, als mit dem neuen Kernel plötzlich X nur noch ohne Grafik-Beschleunigung hochkam. Nach einer Packung debuggen stellte sich raus, dass er die Firmware nicht laden konnte. Nachtigall, ick hör dir trapsen, dachte ich mir, und prüfte, ob /bin/cp das richtige ist. War es. Mhh, nanu. Ich habe also geprüft, ob hotplug das richtige cp aufruft. Tat er, /usr/bin/cp. Komisch. Weiter debugged. Mal ein strace auf /usr/bin/cp gemacht. Und, was soll ich sagen, /usr/bin/cp kopiert bei mir in 32k-Blöcken. Mein embutils-cp benutzt hingegen mmap und kopiert in 4 MB Blöcken. Also habe ich /etc/hotplug/firmware.agent angefasst, damit er /opt/diet/bin/cp aufruft, und was soll ich euch sagen, jetzt ist mein X wieder beschleunigt.
Nur damit ihr mal seht, auf was für einem Treibsand-Fundament dieses Linux gebaut ist.
Wobei wahrscheinlich ich über drei Ecken Schuld bin. Mein coreutils ist Version 7.6, das ist AFAIK aktuell, aber meine hotplug-Shellskripte sind von 2006. Vielleicht gibt es da ein Update inzwischen.
Aber die Ironie, dass dieses Firmware-Interface so verstrahlt ist, dass es erst mit dem Fefe-cp nicht tat, und jetzt mit dem GNU-cp nicht tut, und ich es fixen kann, indem ich das Fefe-cp benutze, das ist schon grotesk. Freut euch alle, dass ihr solche Details nicht sehen könnt bei euren Distributionen. Und, falls das hier jemand liest, der für das Firmware-Laden-Interface im Kernel verantwortlich ist: SCHANDE! SCHANDE! SCHANDE!!
Update: hotplug ist seit Jahren obsolet und sollte ja eh nicht mehr installiert sein, weil es durch udev abgelöst wurde, was ich ja auch installiert habe. Nur leider hat das Vorhandensein von udev bei mir nie dazu geführt, dass der Kernel nicht trotzdem hotplug aufruft. Jetzt habe ich einige Zuschriften gekriegt, die mir erklärt haben, dass man beim Booten
echo > /sys/kernel/uevent_helpermachen muss. Nun starte ich ja beim Booten den udevd, und hätte mir gedacht, hey, wenn ich den da schon starte, dann ist der bestimmt auch so smart, obiges zu tun. Ist er aber nicht.
$ ncftp ftp.samba.orgDas heißt, deren ftpd ist gerade abgestürzt mit den Symptomen, die üblicherweise von buffer overflow Opfern hinterlassen werden. *kaputtlach*
NcFTP 3.2.1 (Jul 29, 2007) by Mike Gleason (http://www.NcFTP.com/contact/).
Connecting to 216.251.47.16…
ProFTPD 1.2.10 Server (ftp.samba.org) [216.251.47.16]
Logging in…
Anonymous access granted, restrictions apply.
Logged in to ftp.samba.org.
ncftp / > cd pub
For web access see http://www.samba.org/ftp/
CWD command successful
ncftp /pub > cd samba
See http://samba.org/ for a list of mirror sites
CWD command successful
ncftp /pub/samba > ls -lrt
Invalid reply: "*** glibc detected *** free(): invalid next size (fast): 0x08110148 ***"
List failed.
Compilation succeeded - 64 warning(s)Überzeugende Technologie!1!!
*** glibc detected *** /tmp/mono-2.2/mono/mini/.libs/lt-mono: free(): invalid pointer: 0x00002aaab0782888 ***
Bei C++ kann man z.B. Boost nehmen, oder die C++ Runtime von gcc hat auch atomare Operationen in ext/atomicity.h.
Also habe ich mir gedacht, hey, kein Problem, MP4Box kann ja auch aneinanderhängen. Ich habe also MP4-Dateien aus Intro, Video und Outro gemacht, und … MP4Box raucht ab, mit einem glibc-Speicherkorruptions-Stackdump, natürlich ein nicht spielbares File hinterlassend.
Gut, dann releasen wir das halt als Matroska. mkvmerge kann auch Tracks hintereinander hängen, genau was man will. Leider mochte er plötzlich meine Intro und Outro nicht lesen, und Zwischen-Hacks der Konvertierung nach Matroska schlugen auch fehl, weil mkvmerge die .AAC-Dateien nicht lesen mochte. Nach dem Encapsulaten in MP4 las er sie dann, aber das Zielvideo hatte kaputtes Audio-Sync. ARGH!
Ich habe jetzt mencoder zerhackt, damit er AVI-Dateien erzeugt, die Vor- und Abspann haben, und richtiges Sync, aber der Sync geht beim Konvertieren nach MP4 oder Matroska wieder verloren.
Inzwischen will ich gar nicht mehr wissen, warum das jetzt alles nicht tut. Ich bin kurz davor, einfach die AVIs zu releasen.
Soweit die Theorie. In der Praxis gibt es verschiedene Ansätze, wie man damit am besten umgeht. Ich habe das Problem in Hardware gelöst, indem ich USB-TV-Grabber mit eingebautem Audio-Sampling gekauft habe, so dass Video und Audio die selbe Taktquelle haben und es keine Sync-Probleme gibt. Nur leider stellt sich heraus, dass mencoder da trotzdem Sync-Probleme herbei halluziniert und alle paar tausend Frames einen Frame dupliziert. Nach zwei Stunden hat man da eine halbe Sekunde Unterschied zwischen Audio und Video.
Gut, dachte ich mir, löse das Problem mit dem Holzhammer, nimm Audio und Video separat auf, dann gibt es da keine Differenzen, die mencoder ausgleichen müsste. Doch weit gefehlt, selbst mit -nosound dupliziert mencoder Frames. Ich könnte kotzen. Jetzt habe ich meinen mencoder gepatcht, damit er auf Zuruf das Fummeln an den Frames läßt, und nehme damit ein paar Stunden lang arte auf, um zu gucken, ob das damit in sync bleibt. Moan ey, wie schwer kann das denn eigentlich sein alles? Offenbar benutzt mencoder da tatsächlich völlig sinnlos auch die PC-Systemzeit mit, die alles andere als zuverlässig ist und vor allem zum Aufnehmen von Video überhaupt gar keine Rolle spielen sollte. Ich erwähne nur mal am Rande "ntp". Laut man-Page müsste man das mit "-mc 0" ausschalten können, aber ich konnte da keine Verhaltensänderung feststellen.
Und falls jetzt jemand sagt, nimm halt was anderes als mencoder: der Treiber ist offenbar weitgehend im Eimer und funktioniert nur mit mencoder. vlc sagt
vlc: [00000272] v4l demuxer error: mmap unsupportedffmpeg sagt
[video4linux2 @ 0x2b068eec8040]The v4l2 frame is 614400 bytes, but 460800 bytes are expectedstreamer (von xawtv) sagt:
setformat: 16 bit YUV 4:2:2 (planar) (640x480): failedDie SVN-Version von vlc segfaultet direkt, noch vor dem Öffnen des Fensters, mit einer glibc-"der Heap ist korrupt"-Meldung.
Und tatsächlich: mit -O2 statt -Os geht es.
Nur der Vollständigkeit halber: diet libc darf man auch mit -fno-inline kompilieren. So ein Pfusch am Bau zählt bei mir als Bug.
Na gut, denke ich mir, guck ich mal in NEWS rein, das ist ja genau die Datei, wo man sowas erwähnt. Was steht da? Gar nichts zum Code in elf oder dem dynamischen Linker. Naja, wäre ja keinen Blog-Eintrag wert, es weiß ja jeder, was für ein Pfuscher das ist, aber dann fand ich das hier von 2004, und da fordert er ja explizit darauf auf, daß man anderswo über ihn meckern soll, und gerne dafür Blogs aufmachen soll. Nun, dafür ist er mir zu unwichtig, daß ich da noch nen Blog aufmachen würde für, aber hey, he had it coming. Ulrich Drepper ist für mich der Anti-Programmierer, der kann noch weniger als dieser Python-Schnösel oder dieser Ruby-Knallkopf. Ich traue den Kerberos- und X11-Leuten mehr Sachkenntnis zu als ihm. Wenn ich einen nennen sollte, der Linux am meisten schadet, ich müsste nicht lange nachdenken. Und bei Redhat ist der Mann auch prächtig aufgehoben, die haben sich gegenseitig verdient.
Hach wie ich das hasse, anderer Leute Bugs fixen zu müssen.
Update: das Problem ging bei mir weg, als ich in sysdeps/i386/i486/bits/atomic.h die Zeile
#if __GNUC_PREREQ (4, 1)gegen
#if __GNUC_PREREQ (4, 2)tauschte. Saubere Qualitätssicherung, Jungs, das habt ihr wirklich prima gemacht. Pah, wenn man eine neue libc rausbringt, warum sollte man da schon prüfen, ob das mit dem aktuellen gcc funktioniert. Wo kämen wir da hin. Linux muss spannend bleiben!
Aber wartet, kommt noch besser! Die Testsuite fällt auf die Fresse. Fehlermeldung:
iconv_prog: malloc.c:2575: do_check_chunk: Assertion `((char*)p) < min_address || ((char*)p) > max_address' failed.Hach, prima!
Nächste Hürde mal wieder der 32-bit Kram. gcc will auf amd64-Systemen auch gleich einen gcc mitbauen, der 32-bit Binaries erzeugt, für "gcc -m32". Schön und gut, allerdings kommt der mit einem Runtime, insbesondere für C++ und Java, und dieses Runtime nimmt dann an, daß es auch die System-Header für 32-bit Systeme vorfindet, was nicht der Fall ist. Insbesondere die Syscall-Nummern sind anders, einige Structs vermutlich auch. Gentoo löst das Problem, indem sie die glibc-Includes aufhacken, so daß die per #ifdef je nach Zielplattform andere Dateien reinziehen. Ein gruseliger Hack, und eben auch non-default. Das hat die glibc echt ganz großartig verkackt, in Zusammenarbeit mit gcc. Und so sind da jedes Mal wieder Eingriffe nötig, um den Scheiß überhaupt kompiliert zu bekommen. Als das dann nach diversen Anläufen doch irgendwann funktionierte, stellte ich zu meiner Überraschung fest, daß gcc jetzt warnt, wenn man "if (i + 3 < i)" macht bei signed ints. Wow. Sollte ich doch mal was erreicht haben mit meinem Kampf gegen die Windmühlen?
glibc installiert seine Header in /usr/include, und hat verschiedene Header für x86 und x86_64. Man kann aber gcc mit -m32 aufrufen, und dann kompiliert er für x86. Leider benutzt er dann immer noch /usr/include. In den meisten Fällen ist das OK, aber manchmal eben nicht, wie z.B. beim Java-Runtime von gcc. Nun hat gcc einen Override-Mechanismus für sowas. Z.B. included er vor /usr/include noch seinen Override-Pfad /usr/lib64/gcc/x86_64-unknown-linux-gnu/4.1.1/include (bei mir). Nun, leider ist auch dieser Pfad der selbe bei -m32.
gcc hat einen Mechanismus, um sowas zu fixen. Man kann ein specs-File erzeugen. Früher hat gcc das mitgeliefert, heute ist das built-in. Man kann das trotzdem noch extrahieren (ruft mal "gcc -dumpspecs" auf, um euch den Horror mal zu geben) und irgendwo geschickt platzieren, aber … wir haben 2007. Liebe gcc-Leute, das ist unter aller Sau. Fixt das mal. Und wenn die glibc-Deppen nicht pro Plattform andere Includes hätten, wäre das auch alles kein Problem. Das liegt im Übrigen u.a. daran, daß glibc die Kernel-Header included. Ich habe das am Anfang auch gemacht bei der dietlibc. Linus himself hat mir dann eine Mail geschrieben, daß ich das nicht machen soll, weil das nur Probleme macht, und hey, rückblickend hatte der Mann sowas von Recht… tja, nur bis zur glibc hat sich das noch nicht rumgesprochen.
Wenn ihr eine 64-bit Distribution fahrt, dann guckt euch mal an, wie die das regelt. Gentoo hat da echt einmal den Hazmat-Anzug angezogen und ein Konstrukt namens "multilib" gebaut. Auf meinem Gentoo-Testsystem sieht z.B. /usr/include/wchar.h so aus:
/* Autogenerated by create_ml_includes() in multilib.eclass */Na, ist das nicht wi-der-lich? Danke, liebe glibc, daß ihr Linux so effektiv kaputt gemacht habt. Nicht einmal offene Eiterwunden wie X oder Samba sind je so tief gesunken.#ifdef __i386__
# include
#endif /* __i386__ */#ifdef __x86_64__
# include
#endif /* __x86_64__ */
Oh, noch nen Nachschlag für die Variante mit dem "specs"-File: wenn ich das anfasse, und da z.B. folgendes bei cpp_options reineditiere:
{m32:-isystem /usr/include32}dann hängt mir das natürlich auch beim dietlibc-für-x86-Kompilieren /usr/include32 in den Pfad und macht alles kaputt. Ihr seht schon, wieso Gentoo diese Würg-Lösung gewählt hat.
if (*p == '*')Sieht ja an sich OK aus, außer man beschäftigt sich mal mit der Zahlendarstellung in Computern. Im Zweierkomplement gibt es eine negative Zahl (0x80000000 auf 32-bit Systemen), für die es keine Positiv-Darstellung gibt. Wenn man diese Zahl negiert, kommt sie wieder selbst raus. Für abs() bedeutet das:
{
++p;
total_width += abs (va_arg (ap, int));
}
abs(0x80000000) == 0x80000000 == -2147483648Für diesen Code heißt das, daß die offensichtlich unterliegende Annahme falsch ist, daß dieser Code immer nur Zahlen drauf addiert. Insbesondere kann man so den Code dazu kriegen, weniger Platz zu allozieren, als dann am Ende reingeschrieben wird.
Was mich ja besonders irritiert: dieser Code kommt von libiberty, das ist Teil von gcc/binutils/gdb. Das ist noch keinem aufgefallen?
Und als ich gerade dabei war, dachte ich mir, mhh, printf liefert die Anzahl der Zeichen zurück, die er hätte schreiben wollen. Als int. Was, wenn das so groß wird, daß es negativ wird? Sollte printf das abfangen? -1 zurück geben? Mal gucken, was die glibc macht:
#include <stdio.h>Wenn man das Programm aufruft, frißt er ein Gig RAM, läuft 17 Sekunden (auf meinem fetten Athlon 64), und schreibt dann -2147483647.int main() {
printf("%d\n",snprintf(0,0,"%*d %*d",0x40000000,1,0x40000000,1));
}
Ich hab das jetzt für die dietlibc so gemacht, daß *printf dann -1 zurück gibt. Offenbar ist das unter Solaris jetzt schon so, und wenn ich nicht der erste bin, der das so macht, dann trifft mich auch keine Schuld wegen der Kompatibilitätshölle :-)
Ich habe auch gerade eine Mail vom mirBSD-Projekt gekriegt, da hat jemand den Eintrag hier gesehen und nen Patch eingespielt, die liefern jetzt auch -1 zurück. Das war fix, keine zwei Stunden Turnaround!
Nun braucht man für dieses Logging die Möglichkeit, das Original-malloc aus der glibc aus dem überschreibenden malloc aufzurufen. Dafür gibt es libdl, wofür man dlfcn.h included und dann dlsym benutzt. Die glibc hat genau dafür ein Flag vorgesehen, RTLD_NEXT, mit dem man dlsym sagen kann, man will das zweite Symbol haben, also das was ohne das LD_PRELOAD genommen worden wäre.
Soweit, sogut, funktioniert auch, — außer das Programm benutzt pthread. Dann gibt es sofort einen Segfault, bevor noch das Logfile erzeugt wird. Stellt sich raus, daß dlsym mit libpthread offenbar selber malloc aufruft. GROOOOOSS, liebe glibc-Leute, GANZ groß. Da weiß man, wieso man glibc benutzt.
Das landet dann natürlich bei meinem malloc-Wrapper, der aber noch nicht weiß, welches Original-Malloc er aufrufen soll. Es gibt sogar eine undokumentierte Lösung für dieses Problem: man ruft __libc_malloc auf, was die glibc genau für solche Situationen zur Verfügung stellt — damit libdl ein malloc aufrufen kann, ohne ein LD_PRELOAD malloc zu erwischen. Aber hey, im in den Fuß schießen war die glibc ja schon immer führend.
Nun kann ich mit einem Speicherleck eher umgehen als mit crashender Infrastruktur, und so habe ich das kurzerhand auskommentiert. Dann crasht git nicht mehr, aber das update schlägt immer noch fehl:
Fetching head…Ihr glaubt ja gar nicht, wie wenig Toleranz ich solcher Kinderkacke gegenüber aufzubringen habe. Ich habe das jetzt nicht genug debugged, um mit Sicherheit sagen zu können, daß curl Schuld ist, aber curl hat mich ja schon damit gegen sich aufgebracht, daß ich nach dem Update alle Applikationen neu linken mußte, die dieses Stück Sondermüll benutzen, insbesondere centericq und ogg123. NERV!!
Fetching objects…
error: Request for c9701c80af9b9c3be27dfe4076280a24aa3059dd aborted
Getting pack list for http://elinks.or.cz/elinks.git/
error: Unable to start request
Getting alternates list for http://elinks.or.cz/elinks.git/
error: Unable to find c9701c80af9b9c3be27dfe4076280a24aa3059dd under http://elinks.or.cz/elinks.git/
Cannot obtain needed none c9701c80af9b9c3be27dfe4076280a24aa3059dd
while processing commit 0000000000000000000000000000000000000000.
progress: 0 objects, 0 bytes
cg-fetch: objects fetch failed
*** glibc detected *** git-http-fetch: double free or corruption (fasttop): 0x08085860 ***Und falls jetzt jemand denkt, ich hätte irgendwas ungezogenes mit git gemacht: ich rief "cg update" auf, im elinks tree.
Was für ein Schock, kompiliert nicht. Der Bug ist in scripts/gen-sorted.awk, wenn man mawk als System-awk installiert hat. Der besteht darauf, daß man in Character Classes in regulären Ausdrücken den Slash escaped. Vermutlich mal wieder eine GNU extension, die sie da ausnutzen. GNU ist echt genau so schlimm wie die BSD-Leute, wenn es um Extensions geht. Genau wie Software von BSDlern nur auf BSDs baut, gehen die davon aus, daß niemand auf der ganzen Welt jemals ihre Software auf etwas anderem als einem GNU-System kompilieren würde. *Kotz*!
Beginnen wir mit dem guten Teil: bsearch und qsort. Das API ist wohldefiniert, funktioniert verläßlich, und taugt auch im täglichen Leben. Gut, es gibt da noch vereinzelt Details, über die man sich lustig machen kann, z.B. daß qsort in der glibc nicht etwa ein Quicksort ist, sondern ein Mergesort :-) Aber hey, darüber kann man hinweg sehen.
Die anderen Kracher sind alle in <search.h>. Es gibt da z.B. so Gemmen wie lsearch und lfind. Beide kriegen wie bsearch eine Vergleichs-Funktion als Function Pointer übergeben und suchen linear in einem Array. Ja, linear. In der Zeit, in der ich eine Vergleichs-Funktion implementiert habe, habe ich das auch schnell inline hingeschrieben. Und lesbarer ist das auch. Wieso gibt es da zwei Funktionen? Weil eine von ihnen im Falle des Nicht-Findens das gesuchte Element am Ende anhängt. Wer sich jetzt denkt, daß die Funktion dann da schon realloc machen wird, täuscht sich. "Wird schon passen". What could possibly go wrong? Oh, welche sucht und welche fügt auch an? Ist anhand der Namen ja schwer zu sagen, gell? Ich zitiere mal die Linux-Manpage: BUGS: The naming is unfortunate.
Aber das ist bestimmt nur ein Ausreißer, mhh? Der Rest der Datei ist bestimmt brauchbar. Na mal gucken… insque und remque überzeugen auch auf Anhieb. Es gibt da zwei VAX-Instruktionen gleichen Namens, die Elemente in eine doppelt verkettete Liste einfügen. Und so erwarten insque und remque Zeiger auf eine struct, die einen next und einen previous Pointer am Anfang haben. Leider kann man das in C nicht ausdrücken, und so kriegen die Funktionen void* übergeben! Type-Check? Brauchen wir nicht. Und es gibt auch kein Konzept einer leeren Liste, und remque kann einem auch nicht mitteilen, wenn die Liste jetzt leer ist. Kurz: voll für den Fuß.
Aber wartet, es kommt noch mehr: hsearch sucht in einer Hash-Tabelle. Ja, einer. Das ist kein Argument an die Funktion, es gibt nur eine, implizite, statisch deklarierte, auf die man auch nicht direkt zugreifen kann. Warum auch, man hat ja hsearch. Und wenn man mehr als eine Hash-Tabelle braucht? Geht nicht. (Es gibt eine GNU Extension, mit der das dann doch geht)
Nun fragt man sich: wer kann so auf Drogen gewesen sein, sich so beschissene APIs auszudenken? Das können doch eigentlich nur die BSD-Junkies gewesen sein? Nein! Das ist System V! Das ist das Stinke-Unix, dessen übelriechenden Kadaver SCO gekauft hat. Es gibt in der Datei auch noch APIs für Suchbäume. Ich habe sie mir noch nicht angesehen, aber es würde mich wundern, wenn die was taugen.
Zuerst muss man dafür Cross-binutils bauen, das sind Assembler und Linker und so, das ist auch normalerweise gar kein Problem.
./configure --prefix=/opt/cross --target=x86_64-linux --enable-static --disable-sharedund gut ist. Statisch deshalb, weil ich in /opt/cross auch Crosscompiler für zig andere Plattformen habe, um zu gucken, ob die diet libc noch mit allen Plattformen sauber baut. Wenn man da den Default nimmt, und ein Cross-Binutils für Alpha-Linux installiert, dann geht danach der Assembler für AMD64 nicht mehr, weil der die selbe Shared Library laden will, die aber von AMD64 nichts weiß. OK, damit kann ich leben. Fällt man einmal drauf rein, lernt man, gut ist.
Der nächste Schritt ist der Crosscompiler. Hier kommt die erste echt Hürde, denn der Crosscompiler baut normalerweise für C, C++, Java, Objective C, allen möglichen Schund Compiler. Die kommen alle mit Laufzeitumgebungen, die nicht kompilieren, weil sie eine funktionierende libc erwarten. Die haben wir nicht, wir bauen ja gerade den Compiler, mit dem wir sie dann übersetzen wollen. Daher sieht bei gcc die Configure-Zeile so aus:
./configure --prefix=/opt/cross --target=x86_64-linux --enable-static --disable-shared --enable-languages=c --disable-nlsDas --enable-static hier bezieht sich auf libgcc; wenn gcc die wie normal dynamisch zu bauen versucht, schlägt das mangels libc fehl. Aber auch so gibt es Ärger, weil libgcc von der Zielplattform die libc-Header haben will. Das finde ich persönlich ja einen groben Verlierer, ja geradezu den Stirnklatscher überhaupt. An der Stelle hat man normalerweise verloren. Übliche Notfall-Ansätze sind "Debian-Paket bzw Fedora-RPM der glibc der Zielplattform ziehen, von Hand die Header auspacken, und an die richtige Stelle (/opt/cross/x86_64-linux/include) kopieren". Wieso nicht einfach /usr/include nehmen? Weil bei der glibc die Header von der Plattform abhängen! Nicht so bei der dietlibc, und so habe ich für meine Crosscompiler immer die dietlibc-Header gesymlinkt, und dann ging das gut (mal abgesehen von Bizarro-Plattformen wie IA64, der will da Dateien und Symbole haben, von denen ich noch nie gehört habe, irgendwelcher libc-Support fürs Stack Unwinding… *grusel*).
Normalerweise höre ich an der Stelle auf, denn wenn ich einen Crosscompiler habe, kann ich damit die dietlibc übersetzen. Heute habe ich mich aber so über das Gentoo auf meinem Desktop geärgert, daß ich mir vorgenommen habe, meine Linux-Distro "Fefix" mal für AMD64 zu crosscompilen. Schritt 1: glibc. Und was soll ich euch sagen, glibc ist nicht crosscompilefähig! Der versucht, sizeof(long double) rauszufinden, indem er ein Programm übersetzt und ausführt. Das geht natürlich nicht. Gut, in configure fest 16 reingehackt (die Alternative ist, einen config.cache manuell zu erstellen, in dem das drin steht), und was sehen meine entzündeten Augen?
checking for libgd… no [libgd?!?!?…]WTF?! C cleanup handling? Und gcc 4.1.1 soll das nicht unterstützen?! ARGH! Na gucken wir uns doch mal das Testprogramm an:
checking for ANSI C header files… no [doh!]
checking for sys/types.h… no
checking for sys/stat.h… no
checking for stdlib.h… no
checking for string.h… no
checking for memory.h… no
checking for strings.h… no
checking for inttypes.h… no
checking for stdint.h… no
checking for unistd.h… no
checking for long double… no
checking size of long double… (cached) 16 [Warum macht er das überhaupt, wenn er glaubt, daß es kein long double gibt?!]
running configure fragment for sysdeps/x86_64/elf
checking for x86-64 TLS support… yes
running configure fragment for nptl/sysdeps/pthread
checking for forced unwind support… yes
checking for C cleanup handling… no
#include <stdio.h>Na und woran scheitert es wohl? RICHTIG! Kein stdio.h! Denn ich kompiliere ja gerade die libc, DAMIT ICH DIESES HEADER-FILE KRIEGE! Lieber Herr Drepper, warum erhängen Sie sich nicht einfach an der nächsten Eiche? Das würde uns allen viel Ärger sparen. Danke.
void cl (void *a) { }
int
main ()
{
int a __attribute__ ((cleanup (cl)));
puts ("test")
;
return 0;
}
Und Apple weiß das offensichtlich auch: "How to pick up and carry your iMac G5"
Ich überlege, ob ich mal einen Wettbewerb ausschreibe, wer die beste Implementation von strfry und memfrob für libcompat in der diet libc macht… Das wäre im Übrigen alles nur halb so lächerlich, wenn Herr Drepper nicht strlcpy und strlcat den Zutritt in die glibc verweigert hätte, und als Alternative ernsthaft folgendes vorschlägt:
*((char *) mempcpy (dst, src, n)) = '\0';
But wait, there's more! zu strfry gibt es auch noch ein Security Advisory (das ich allerdings für hirnrissig halte, das könnte man genau so gut bei strlen bemängeln), und der absolute Oberhammer ist dieser Debilian Bugreport. Un-fucking-believable. Wenn es ein Code-Killfile gäbe, Ulrich Drepper wäre drin.
Oh, und wo wir gerade beim glibc-Bashing sind: man kann bei der glibc Callbacks definieren, um bei printf neue Format-Zeichen zu definieren. Und nun ratet mal, welche Qualitätssoftware dieses großartige Feature benutzt: richtig, reiserfsprogs. Die definieren damit z.B. einen Callback, um ein Tupel von zwei Zahlen auszugeben IIRC. Unfaßbar. Aber hey, reiserfs benutzt ja eh niemand, oder? Oder?!
Vielleicht sollte ich echt mal eine Webseite aufmachen, und dort ein Coder-Killfile unterhalten. Drepper, die Reiserfs-Jungs, Schilling, … da kämen aber nur die besonders harten Fälle hin. Ich will ja auch noch zu anderen Sachen kommen.