Thursday, July 22. 2010
Introduction
In the beginning, there was char. It could hold an ASCII character and then some, and that was good enough for everybody. But later, the need for internationalization (i18n) and localization (l10n) came up, and char wasn't enough anymore to store all fancy characters. Thus, multi-byte character encodings were conceived, where two or more chars represented a single character. Additionally, a vast set of incompatible character sets and encodings had been established, most of them incompatible to each other. Thus, a solution had to be found to unify this mess, and the solution was wchar_t, a data type big enough to hold any character (or so it was thought).
Multi-byte and wide-character strings
To connect the harsh reality of weird multi-byte character encodings and the ideal world of abstract representations of characters, a number of interfaces to convert between these two was developed. The most simple ones are mbtowc() (to convert a multi-byte character to a wchar_t) and wctomb() (to convert a wchar_t to a multi-byte character). The multi-byte character encoding is assumed to be the current locale's one.
But even those two functions bear a huge problem: they are not thread-safe. The Single Unix Specification version 3 mentions this for wctomb, but not for mbtowc, while glibc documentation mentions this for both. The solution? Use the equivalent thread-safe functions mbrtowc and wcrtomb. Both of these functions keep their state in a mbstate_t variable provided by the caller. In practice, most functions related to the conversion of multi-byte strings to wide-character strings and vice versa are available in two versions: one that is simpler (one function argument less), but not thread-safe or reentrant, and one that requires a bit more work for a programmer (i.e. declare mbstate_t variable, initialize it and use the functions that use this variable) but is thread-safe.
Coping with different character sets
To convert different character sets/encoding between each other, Unix provides another API, named iconv(). It provides the user with the ability to convert text from any character set/encoding to any other character set/encoding. But this approach has a terrible disadvantage: in order to convert text of any encoding to multi-byte strings, the only standard way that Unix provides is to use iconv() to convert the text to the current locale's character set and then convert this to a wide-character string.
Assume we have a string encoded in Shift_JIS, a common character encoding for the Japanese language, and ISO-8859-1 (Latin 1) as the current locale's character set: we'd first need to convert the Shift_JIS text to ISO-8859-1, a step that is most likely lossy (unless only the ASCII-compatible part of Shift_JIS is used), and only then we can use to mb*towc* functions to convert it to a wide-character string. So, as we can see, there is no standard solution for this problem.
How is this solved in practice? In glibc (and GNU libiconv), the iconv() implementation allows the use of a pseudo character encoding named "WCHAR_T" that represents wide-character strings of the local platform. But this solution is messy, as the programmer who uses iconv() has to manually cast char * to wchar_t * and vice versa. The problem with this solution is that support for the WCHAR_T encoding is not guaranteed by any standard, and is totally implementation-specific. For example, while it is available on Linux/glibc, FreeBSD and Mac OS X, it is not available on NetBSD, and thus not an option for truly portable programming.
Mac OS X (besides providing iconv()) follows a different approach: in addition to the functions that by default always use the current locale's character encoding, a set of functions to work with any other locale is provided, all to be found under the umbrella of the xlocale.h header. The problem with this solution is that it's not portable, either, and practically only available on Mac OS X.
Alternatives
Plan 9 was the first operating system that adapted UTF-8 as its only character encoding. In fact, UTF-8 was conceived by two principal Plan 9 developers, Rob Pike and Ken Thompson, in an effort to make Plan 9 Unicode-compatible while retaining full compatibility to ASCII. Plan 9's API to cope with UTF-8 and Unicode is now available as libutf. While it doesn't solve the character encoding conversion issue described above, and assumes everything to be UTF-8, it provides a clean and minimalistic interface to handle Unicode text. Unfortunately, due to the decision to represent a Rune (i.e. a Unicode character) as unsigned short (16 bit on most platforms), libutf is restricted to handling Unicode characters of the Unicode Basic Multilingual Plane (BMP) only.
Another alternative is ICU by IBM, a seemingly grand unified solution for all topics related to i18n, l10n and m17n. AFAIK, it solves all of the issues mentioned above, but on the other side, is perceived as a big, bloated mess. And while its developers aim for great portability, it is non-standard and has to be integrated manually on the respective target platform(s).
Monday, July 12. 2010
OpenBSD ist - wie dem einen oder anderen Leser sicherlich bekannt sein dürfte - ein von NetBSD abgeleitetes Unix-artiges Betriebssystem, das seinen Fokus auf Sicherheit legt. Als Entwickler und Maintainer des mittlerweile halbwegs populären Open-Source-Projekts newsbeuter sehe ich es nicht nur als meine Aufgabe, neue Features zu entwickeln und bestehende Bugs zu fixen, sondern auch einen gewissen Aufwand darin zu stecken, die Software für ein möglichst großes Publikum auch tatsächlich praktisch zugänglich zu machen. Diese Aufgabe hat verschiedene Grundrichtungen. Das ist etwa die einfache Verfügbarkeit für Enduser (da habe ich beispielsweise vor kurzem erst eine Liste von Distributionen, die mit fertigen Paketen kommen, zusammengestellt), eine möglichst durchgängige Internationalisierung und Lokalisierung (so ist newsbeuter in mittlerweile 14 verschiedenen Sprachen verfügbar), oder aber auch, die Kompatibilität zu anderen Systemen als nur die populären Linux-Distributionen zu testen und zu gewährleisten. Letzteres hat dazu geführt, dass newsbeuter unter Linux, FreeBSD und Mac OS X läuft.
Vergangenes Wochenende habe ich mich daran gemacht, die Kompatibilität auch zu weiteren Systemen zu testen. Auf meinem Plan standen konkret NetBSD und OpenBSD. Der Zustand von NetBSD ist kurz geschildert der, dass die Citrus-Implementierung von iconv() in der Art und Weise nicht kompatibel ist, als dass es das spezielle Encoding "WCHAR_T", das von der STFL verwendet wird, nicht unterstützt. Das ist nicht schön, aber verkraftbar, da einerseits die von iconv unterstützten Encodings/Zeichensätze systemspezifisch sind, andererseits man da sicherlich auch (achtung, ungetestet!) die GNU libiconv einsetzen könnte.
Bei OpenBSD bietet sich hier ein völlig anderes Bild: hier scheiterte mein Versuchen, die STFL zu übersetzen, schon daran, dass OpenBSD über keine Implementierung von swprintf verfügt. Etwas verdutzt hab ich dann begonnen, weiterzurecherchieren, weil ich eigentlich der Meinung war, dass OpenBSD eigentlich schon mal die Arbeit des Citrus-Projekts importiert hatte, und bin schon nach kurzem über die nahezu talibanesken Ausführungen eines Herrn uriel, der einen Fanatismus dabei zeigt, auf den Unix-Standardweg für Internationalisierung (nämlich wchar_t + Funktionen darauf) zu verzichten und stattdessen UTF-8 als den Einzig Wahren Weg(TM) anzupreisen. Schließlich habe ich mich rangemacht, und geschaut, welche Funktionen aus wchar.h in NetBSD (die Citrus integriert haben), jedoch nicht in OpenBSD zu finden sind, und bin auf folgendes Ergebnis gestoßen. Dadurch, dass das nicht irgendwelche Pipifax-Funktionen sind, sondern da auch ein konkreter Standard bzw. hinreichend verbreitete Implementierungen dahinterstehen, ist die Liste nach Standards sortiert:
Single Unix Specification, Version 2- fwprintf
- fwscanf
- swprintf
- swscanf
- vfwprintf
- vswprintf
- vwprintf
- wprintf (wird aber in der Manpage von wcstok(3) erwähnt...)
- wscanf
Single Unix Specification, Version 3
Proprietär, aber in NetBSD und glibc zu finden- wcsdup
- wcsncasecmp
- wcscasecmp
Man beachte, dass die allermeisten Funktionen schon in SuSv2 spezifiziert sind und es als solche auch in C99 reingeschafft haben.
Tja, und da war es dann aus bei mir mit der guten Laune. Ist es tatsächlich zuviel verlangt von einem doch vergleichsweise populären Betriebssystem (gemessen an der Open-Source-Community, nicht der Gesamtheit aller Computeruser weltweit), etablierte Standards, die thematisch eigentlich genau in die Projektzielsetzung von OpenBSD fallen, umzusetzen, oder ist OpenBSD neuerdings dazu übergegangen, ein möglichst kastriertes System, das entfernt noch irgendwie an Unix erinnert, zu entwickeln? Es ist ja nicht so, dass die Komplexität der oben genannte Funktionen besonders groß wäre, immerhin gibt es davon ja fertige Implementierungen, die sogar in einer für OpenBSD akzeptablen Lizenz vorliegen (nämlich die Entwicklungen des Citrus-Projekts).
Unter solchen Voraussetzungen, dass nicht einmal 13 Jahre alte Standards aus dem C- und Unix-Umfeld umgesetzt werden, kann ich auf jeden Fall OpenBSD nicht mehr als ernstzunehmendes Unix-artiges Betriebssystem betrachten. Ich zeige kein Verständnis für Extrawürste und bewusst weggelassene Funktionalität. Solange nicht zumindest swprintf() in OpenBSD existiert, ist newsbeuter-Support für OpenBSD gestorben, explizit auch so dokumentiert, und ich kann nur jedem, der ernsthafte Open-Source-Entwicklung betreiben will, von einer angestrebten Kompatibilität mit OpenBSD abraten, weil dafür im Vergleich zu anderen System riesige Kompromisse eingegangen werden müssten.
Tuesday, October 23. 2007
Auf die wirklich ekligen Sachen kommt man erst drauf, wenn man versucht, Unix-Legacy-Code auf einem aktuellen Linux oder OSX zum Laufen zu bringen. Da bin ich z.B. draufgekommen, dass es in C unter Linux ein Symbol "end" gibt (wer es zur Verfügung stellt, konnte ich ad hoc nicht rausfinden), auf jeden Fall muss man es nur deklarieren, und schon ist es verwendbar: #include <stdio.h> extern char end[1]; int main(void) { return 0; }
Draufgekommen bin ich darauf nur, weil ich sort(1) von Ultrix-11 auf Linux portiert habe, und diesen Code wiederum unter OSX zum Laufen bringen wollte. Und dabei ist mir folgender Code untergekommen: #define MEM (16*2048) /* ... */ char *ep; /* ... */ ep = end + MEM; /* ... */ while((int)brk(ep) == -1) ep -= 512; #ifndef vax brk(ep -= 512); /* for recursion */ #endif
Dieser Code legt nahe, dass "end" das aktuelle Ende des Datensegments enthält, und obiger Code holt sich das Ende, zählt einen großen Betrag (für damalige Zeiten...) dazu, und verringert ihn solange um 512 (welche Einheit?), bis brk(2) OK gegangen ist, und das Datensegment vergrößert ist. Sowas ist in Zeiten von 4GB+ RAM und Memory Overcommitment natürlich höchst obsolet. Trotzdem ist es bemerkenswert, wie damals(tm) noch programmiertechnisch gearbeitet wurde.
Friday, March 30. 2007
Nachdem das Ranten über GNU so schön ist, hier noch ein kleiner Nachschlag. Wieder fast jeder, der schon mal Software unter Linux selbst compiled hat, weiß, muss man vor dem Übersetzen meist noch ./configure aufrufen, welches dann ein paar systemspezifische Konfigurationsinformationen sammelt und ein Makefile generiert.
Zum Erstellen dieses configure-Skripts gibt es ein Tool namens autoconf. Dieses generiert aus einem configure.in bzw. configure.ac File das configure-Skript. Das configure.ac wiederum kann man sich aus dem bestehenden Sourcetree generieren lassen und dann händisch anpassen. Das Makefile, das man dann zum Compilieren benötigt, wird übrigens vom configure-Skript i.A. aus dem Makefile.in erzeugt, wobei hierbei Platzhalter durch die entsprechenden ermittelten bzw. gesetzten Werte (./configure lässt sich ja relativ gut durch Commandline-Switches steuern) ersetzt werde. Makefile.in's schreiben sich ähnlich wie Makefiles. Und weil das Schreiben von Makefile.in's sooo schwer ist, gibt es auch gleich noch ein weiteres Tool, automake, mit dem man sich das Makefile.in aus einem Makefile.am erzeugen lassen kann.
Durch einen jahrelangen Reifungsprozess sind autoconf und automake schon so gut integriert, dass sie fast eine Symbiose bilden. Diese Symbiose ist so gut, dass es vom GNU-Projekt keine Dokumentation gibt, wie man autoconf ohne automake verwendet. Das heisst, jeder enthusiastische Programmierer, der auf die Vorteile von autoconf zurückgreifen will, und sich das selbst anhand der Doku aneignet, wird auch automake verwenden. Am Ende der Integration von autoconf und automake ins eigene Sourcepaket bleiben dann ein ca. 200 kB grosses configure-Skript und ein 50 kB grosses Makefile.in übrig, sowie noch 50 - 100 kB andere Dateien wie z.B. diverse Helper-Shellskripte. Das ist natürlich sehr toll, wenn das eigene, selbstgeschriebene Tool aus vielleicht 15 kB Sourcen besteht.
Aber trotzdem gibt es Mittel und Wege, um ohne automake auszukommen, was die Gesamtgröße der generierten Dateien um einiges herabsetzt. Wie das im Detail geht, wäre jetzt zuviel des guten, jedoch nur ein kurzer Tipp: wer Makefiles schreiben kann, und /usr/local durch prefix und gcc durch CC ersetzen kann, der kann Makefile.in's schreiben, und braucht kein automake.
Hier noch mal die Erklärung: autoconf sorgt für das configure-Skript, das Systemkonfiguration sammelt und das Makefile erzeugt, und automake sorgt für das Makefile.in-Template, sodass der Programmierer sich um weniger Makefile-Details kümmern muss (muhahahaha). Eigentlich eine gute Trennung. Theoretisch. Wie sieht es praktisch aus? Nun, das Dokumentationsproblem habe ich schon erwähnt, aber etwas wesentlich perfideres hab ich heute wieder mal entdecken müssen: autoconf ist ja m4-basiert, und bringt einige fertige m4-Makros zum Testen auf bestimmte Konfigurationen mit. Allerdings gibt es noch wesentlich mehr Makros. Zum Beispiel bei automake. Bei automake sind wirklich jede Menge m4-Makros dabei, die wesentlich mehr können, und die einfach eingebunden werden können. Welche Möglichkeiten hat Joe Average Programmer hier? Entweder er kopiert die m4-Makros aus dem automake-Paket in seinen eigenen Sourcetree, oder aber integriert einfach automake in sein eigenes Buildsystem.
Warum GNU diese Makros nicht zu autoconf dazugepackt hat? Nun, das weiß nur Gott ("Gott? welcher Gott?"). Auf jeden Fall haben diese Beobachtungen bei mir den schalen Nachgeschmack hinterlassen, GNU will möglichst viele Programmierer mit autoconf und automake "zwangsbeglücken". Mittlerweile ist es ja fast nicht mehr möglich, ohne das Konfigurieren des Sourcetrees auf Systemeigenheiten noch portable Programme zu schreiben, die "out of the box" unter einem Allerwelts-Unix wie Linux, *BSD und Solaris compilieren und linken. GNU hat sicherlich seines dazu beigetragen, und bringt gleichzeitig ein "Wundermittel" mit, das halt zu 300 kB Scheisse in jedem Sourcepaket führt. Leider sind autoconf und automake als de-facto-Standard etabliert, andere Buildsysteme dagegen finden kaum Beachtung oder Einsatz.
Saturday, August 12. 2006
I just submitted my paper for 23C3. This year I wrote a paper about trapdoor2, which I wrote together with Clifford. I will focus on its implementation and use it as an example to show what attack vectors against a network server (and especially trapdoor2) exist, and what techniques can be employed to encounter potential attacks. Look forward to a pretty interesting lecture, showing some state of the art techniques in secure network server programming in C on Unix and Unix-like operating systems.
Saturday, December 3. 2005
Time for some BSD bashing.
A few days ago, I got a bug report for akpop3d, the POP3 server that I wrote a few years ago. The author of that mail told me that akpop3d on FreeBSD only binds to a tcp6 socket, and thus is not usable from IPv4 networks. Well, that sounded very strange to me, but I did some research on that topic, and that's the reason for this strange behaviour:
In akpop3d, I implemented a mechanism for getting a server socket that tries out all available socket types, and uses the first one that binds successfully. Why? First of all, because Unix Network Programming, IMHO the reference on network-related programming on Unix-like operating systems, says so. The reason stated in this book for why the code is how it is is that this is the way to be as independent from the available socket types as possible, or short: with that code, the program both works with IPv6 (if available) and IPv4.
So, as a consequence, when IPv6 is available as socket type, akpop3d tries to bind to it. Now, one cool IPv6 feature comes into play, and that is "IPv4-mapped IPv6 addresses", which according to RFC 2553 is there to provide interoperability between IPv4 and IPv6. Yes, that's right, interoperability. This means that when you bind to an IPv6 socket, programs and other hosts that don't speak IPv6 yet are able to connect to that IPv6 service, with the operating system working as a mediator. For the server it's always IPv6, for the client IPv4, and both sides are happy.
Now, this all sounds pretty good, so what's the reason behind the bug report that I mentioned before? Well, a few years ago, itojun, OpenBSD's KAME hacker, wrote a paper with the title " IPv4-mapped address considered harmful", where he claimed that IPv4-mapped addresses would bear a security risk, and so OpenBSD decided to disable the IP6_V6ONLY socket option by default (normally, it's enabled, also enabling that interoperability thing). What I found especially interesting about those security risk claims was that nobody really challenged this, except for Felix von Leitner, who wrotes in his remarks on some BSD scalability tests: That's what itojun has said for ages. When I challenged him to point to even one case that demonstrated anyone was ever negatively impacted by the normal behaviour, he posted a message to bugtraq asking for people to step forward. Nobody did. The executive summary of this whole "IPv4-mapped addresses insecure"-hype is that somebody could send you an IPv6 packet with an IPv4-mapped source address, creating an ambiguity (::ffff:127.0.0.1 could be interpreted as coming from localhost) and thus a security hole, and so the way OpenBSD chose to deal with this problem was to disable IPv4-mapped addresses altogether. Hello?! That mechanism is useful even if you don't use IPv6 networking, simply because of interoperability.
So I did some further research, and found out that not only OpenBSD, but also FreeBSD and NetBSD had switched their behaviour, although it seems that on FreeBSD and NetBSD, the default behaviour is still configurable.
To make my point: this just sucks. I'm not willing to do any workarounds for operating systems that deliberately chose to be broken, and only create additional work with no new real outcome.
Monday, November 21. 2005
Today, Nico asked me about how the dc(1) shell magic a la 'echo 28832971807093258P | dc' works. I didn't know it in the first place, but after some manpage reading, I understood it, and tried to explain it to him. I don't know whether he fully understood it (and if not, it was definitely not his fault), but my next idea was to automatize that, i.e. write a script that takes one string and automatically generates the shell one-liner to print out that string. And, well, here it is: span style="color: #666666; font-style: italic;">#!/bin/bash "ibase=16; $(echo -n "" | hexdump -e '"" 4/1 "" ""' | sed 's/ //g')0A""echo ${output}P | dc"
And here's how it works:
$ ./dc-fun.sh ak@synflood.at
echo 505828119445447424943077054048465930P | dc
$ echo 505828119445447424943077054048465930P | dc
ak@synflood.at
$
|