Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 47
Kern-Technik
Sicherheitsbewusste Kernelhacker beäugen skeptisch Netzwerkapplikationen, die in den Kernel wandern sollen. Manchmal gibt\'s dafür aber gute Gründe - Performance etwa.
Sicherheitsbewusste Kernelhacker beäugen skeptisch Netzwerkapplikationen, die in den Kernel wandern sollen. Manchmal gibt\'s dafür aber gute Gründe - Performance etwa.
Wenn Anwender besondere Anforderungen an den Leistungsdurchsatz legen, kann die Verlagerung von Code aus dem Userland in den Kernel sinnvoll sein. Die Maßnahme spart Kontextwechsel-Zeiten ebenso wie aufwändige Kopieraktionen zwischen User- und Kernelspace. Allerdings hebt sie die sinnvolle Trennung von Applikation und Kernel auf. Die Applikation im Kernel hebelt die sonst vorhandenen Schutzmechanismen aus, öffnet im Fall von Programmierfehlern Crackern ein direktes Tor zum Kernel und gerät daher stets zur Speziallösung.
Dennoch lässt sich die Leistungssteigerung für eigene Netzwerkanwendungen im Kernel nutzen. Kleiner Nebeneffekt der Technik: Sämtliche Komponenten einer solchen Anwendung lassen sich in einem Stück Software, nämlich einem Kernelmodul, verpacken. Wo früher also ein oder mehrere Applikationsteile zu installieren waren, lädt der Anwender nur noch ein distributionsunabhängiges Kernelmodul nach.
Doch ist eine Applikation im Kern nicht so einfach zu programmieren wie eine Userspace-Anwendung, weil Linux von dort keinen Zugriff auf normale Bibliotheksfunktionen ermöglicht [1]. Spannenderweise haben die Kernelentwickler gerade für den Netzwerkzugriff aber einen ganzen Reigen von Funktionen geschrieben. Den dürfen sogar Programmierer nutzen, die ihre Software nicht unter die GPL stellen.
Die Funktionen kreieren und löschen Sockets, binden sie an Ports, warten auf eingehende Verbindungen oder bauen solche auf (siehe Tabelle 1). Aus der Bandbreite der Userspace-Funktionen steht also auch im Kernel das Wichtigste zur Verfügung (siehe Abbildung 1).
Entwicklern fallen vier Umstände auf: Erstens haben die Kernel-Pendants andere Namen im Vergleich zu ihren Userspace-Geschwistern, zweitens sind die Aufrufparameter unterschiedlich. Der dritte Unterschied betrifft die Funktionen »read()« und »write()« zum Datenaustausch: Diese Funktionen sucht der Entwickler im Kern vergeblich und muss sie selbst implementieren. Dazu bieten viertens die Funktionen »socket_sendmsg()« und »kernel_sendmsg()« ihre Dienste an - mit auf den ersten Blick ähnlicher Funktionalität.
Einfach anwenden lassen sich die Funktionen »kernel_sendmsg()« und »kernel_recvmsg()«. Beiden übergibt der Kernel-Netzwerker neben dem Socket noch eine Datenstruktur vom Typ »struct msghdr«, die bei verbindungslosen Sockets unter anderem auch die Zieladresse für das Paket enthält (siehe Abbildung 2). Die Daten müssen nicht zwangsläufig an einem zusammenhängenden Speicherort liegen. Vielmehr übergibt der Entwickler ein Feld vom Typ »struct kvec«, das Adressen und Längen der zugehörigen Speicherorte enthält. »kernel_sendmsg()« und »kernel_recvmsg()« kümmern sich darum, dieses Feld in die »struct msghdr« einzuhängen.
Abbildung 2: Die Datenstrukturen für den Datenaustausch erfordern genaues Hinsehen beim Programmieren.
Bei den Funktionen »sock_sendmsg()« und »sock_recvmsg()« hängt der Programmierer die Speicherort-Adressen - jetzt vom Typ »struct iovec« - vor dem Aufruf in den Messageheader ein. Das allein ist aber nicht der entscheidende Unterschied. Systemcalls wie die zwei »sock_«-Funktionen sind qua Implementierung darauf geeicht, Daten zwischen Kernel- und Userspace auszutauschen.
Der Transfer von Kernel zu Kernel ist hingegen nur in seltenen Fällen notwendig. Daher muss der Entwickler vor dem Aufruf der zum Systemcall gehörenden Kernelfunktion die Speicherverwaltung mit einem Trick überlisten. Es gilt, sie zu überreden auch aus dem Kernel selbst einen Zugriff zu gestatten.
Das zugrunde liegende Problem und die Lösung stellt der Kasten "Datentransfer innerhalb des Kernels" vor. Falls sich die in »iov« spezifizierten Speicherorte im Kernelspace befinden und der Entwickler den Systemaufruf »sock_sendmsg()« verwendet, bettet er ihn in die in Listing 1 dargestellte Codesequenz ein.
|
Datentransfer innerhalb des |
|---|
| Systemcalls, die Dienste des Betriebssystemkerns, tauschen Daten zwischen Userspace und Kernelspace vorwiegend mit den Funktionen »copy_to_user()« und »copy_from_user()« aus [2]. Sie verarbeiten Adressen im Userspace, die von der Applikation stammen. Sie sind aus Sicht des Betriebssystemkerns alles andere als vertrauenswürdig. Daher überprüft die Speicherverwaltung vor einem Datentransfer, ob die Adressen gültig sind, ob sie nicht in der so genannten Zeropage liegen oder ob die Adressen nicht ungewollt (oder von einem Malevolenten gar manipuliert) auf eine Adresse im Kernelspace zeigen. Das wäre ein Wert oberhalb der Grenze zwischen User- und Kernelspace. Damit ein Systemcall Daten aus dem Kern akzeptiert, verschiebt der Entwickler diese Grenze kurzfristig. Linux legte sie früher im FS-Register der x86-CPU ab. Wegen dieser Geschichte heißen bis heute die Funktion, die die Grenze auslesen und einstellen, »get_fs()« und »set_fs()«. Vor dem Systemcall aus dem Kernel rettet der Programmierer also den alten Wert und erlaubt temporär mit »KERNELDS« den internen Zugriff. Er darf natürlich nach dieser Operation nicht vergessen, die ursprünglichen und sicheren Werte wieder zu reaktivieren. |
|
Listing 1: Zugriff auf |
|---|
01 msg.msg_iov = &iov; 02 msg.msg_iovlen = 1; 03 04 oldfs = get_fs(); // Grenze sichern 05 set_fs(KERNEL_DS); // Aushebeln des Schutzmechanismus 06 len = sock_sendmsg(sock, &msg, len); 07 set_fs(oldfs); // Restauration des Ursprungszustands |
Außerdem gilt für Netzwerkcode im Linux-Kern, dass viele Zugriffe auf das Netz-Interface nur im Prozesskontext, nicht aber innerhalb einer Interrup-Service-Routine (ISR), eines Tasklet oder einer Timerfunktion statthaft ist. Das Programm muss sich also entweder den Prozesskontext einer Applikation borgen (zum Beispiel den des Programms »insmod«) oder aber mit Hilfe der Funktion »kernel_thread()« eine unabhängige Task aufziehen.
Umfang: 5 Heftseiten
Preis € 0,99
(inkl. 19% MwSt.)
Alle Rezensionen aus dem Linux-Magazin
Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...