Open Source im professionellen Einsatz

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 – Folge 58

Kern-Technik

,

Der Linux-Kernel bietet seine Verschlüsselungsfunktionen auch zum asynchronen Zugriff an. Ineinander geschachtelte Datenstrukturen und undokumentierte Funktionen machen dem Programmierer allerdings das Leben schwer. Diese Kern-Technik schafft Abhilfe.

Wenn die Kernelentwickler von Datentransformation sprechen, meinen sie damit das Ver- und Entschlüsseln, Komprimieren oder das Bilden von Hashsummen. Geeignete Interfaces stellen den Zugriff auf die dafür implementierten Algorithmen sicher, die ihrerseits über definierte Schnittstellen an das Crypto-Subsystem des Linux-Kernels andocken.

Synchron – asynchron

Bisher finden die Transformationen vorwiegend synchron statt, wie es die vorige Ausgabe der Kern-Technik beschrieben hat [1]: Ein User, beispielsweise die Festplattenverschlüsselung, übergibt einen Verschlüsselungsauftrag an das Crypto-Subsystem und wartet bis zum Abschluss der Transformation. Erst dann erteilt er den nächsten Auftrag. Hardware-Verschlüsselung oder Parallelverarbeitung kommen dabei aber nicht zum Zug. Performanter geht es mit dem asynchronen Zugriff, wie ihn auch die Diplomarbeit von Sebastian Siewior [2] vorstellt.

Asynchron bedeutet, dass der User dem Crypto-Subsystem einen oder mehrere Aufträge zum Bearbeiten übergibt und die erfolgreiche oder vergebliche Auftragsbearbeitung zu einem späteren Zeitpunkt prüft (Abbildung 1). Der angeforderte Algorithmus wiederum muss die Aufträge in die Warteschlange einreihen und starten. Die Nachricht über Ge- oder Misslingen teilt das Crypto-Subsystem dem User später mit, indem es eine Callback-Funktion aufruft.

Abbildung 1: Der synchrone Zugriff (oben) auf das Crypto-API des Kernels bringt Wartezeiten mit sich. Bessere Performance verspricht die asynchrone Variante (unten).

Abbildung 1: Der synchrone Zugriff (oben) auf das Crypto-API des Kernels bringt Wartezeiten mit sich. Bessere Performance verspricht die asynchrone Variante (unten).

In der Theorie mag das recht einfach erscheinen, in der Praxis erweisen sich ineinander geschachtelte Datenstrukturen und nicht dokumentierte Funktionen als Stolpersteine. Grundwissen und Quellcodestudium helfen weiter.

Dabei ist für jene, die performant verschlüsseln möchten, der erste Schritt noch einfach: Er spezifiziert den gewünschten Algorithmus und reserviert mit dieser Information das Transform-Objekt vom Typ »ablkcipher_tfm« . Existiert für den Algorithmus keine asynchrone Implementierung, gibt die Reservierungsfunktion einen Wert ungleich 0 zurück. Im anderen Fall wird der zu verwendende Schlüssel an das Transform-Objekt gebunden (Abbildung 2, (1)).

Abbildung 2: Softwarestruktur eines im Kernel asynchron zugreifenden Users.

Abbildung 2: Softwarestruktur eines im Kernel asynchron zugreifenden Users.

Der nachfolgende Schritt bereitet die Transformations-Aufträge und Übergabe an das Crypto-Subsystem vor. Dazu reserviert der User ein Request-Objekt und initialisiert es mit den Adressen der Quell- und Zielspeicherbereiche (als Scatter-Gather-Listen, siehe dazu [1]) und einer Callback-Funktion (Abbildung 2, (2)). Diese spielt eine zentrale Rolle für den dritten Block, der das Durchführen des Auftrags überwacht (3).

Hierbei fällt auf, dass das Crypto-Subsystem einen Auftrag manchmal auch synchron abarbeitet. Ob der Auftrag direkt abgearbeitet (synchron) oder zur Abarbeitung in die Queue gestellt wurde, ist am Rückgabewert der Funktion zur Auftragsvergabe ersichtlich. Im ersten Fall gibt die Funktion 0 zurück, sonst »-EINPROGRESS« oder »-EBUSY« .

Im Fall der asynchronen Abarbeitung ruft das Crypto-Subsystem nach dem Bearbeiten die Callback-Funktion auf und übergibt ihr die Adresse des Auftragsobjekts. Liegen die Ergebnisse sämtlicher Aufträge vor, beginnt im vierten Schritt das Aufräumen: die Freigabe aller Auftragsobjekte, aber auch des Transform-Objekts (Abbildung 2, (4)).

Entschlüsseln

Listing 1 zeigt den Einsatz der asynchronen Schnittstelle. Das Beispiel dekodiert eine Geheimnachricht (Ciphertext), die per XOR-Verfahren mit dem Key »Mein Schluessel.« verschlüsselt wurde. Um den Code möglichst übersichtlich zu halten, beschränkt sich die normalerweise aufwändige Verwaltung der Request-Objekte (»struct ablkcipher_request« ) auf die Reservierung und Freigabe eines einzelnen Objekts. Zum Entschlüsseln einer Nachricht, die kürzer als 16 Byte ist, reicht das aus.

Listing 1

Asynchrone Verschlüsselung (ablk_user.c, Teil 1)

001 #Include <linux/crypto.h>
002 #include "internal.h"
003 #include <linux/scatterlist.h>
004
005 #define CIPHERTEXT "\x01\x0c\x07\x1b\x58\x73\x2e\x09"\
006     "\x0b\x14\x1f\x1a\x1d\x65\x6c\x2e"
007
008 static char *key = "Mein Schluessel.";
009
010 struct crypt_result {
011   struct completion completion;
012   int err;
013 };
014
015 static void request_done(struct crypto_async_request *req, int err)
016 {
017   struct crypt_result *res = req->data;
018
019   printk("request_done()\n");
020   if (err == -EINPROGRESS)
021     return;
022   res->err = err;
023   complete(&res->completion);
024 }
025
026 static int __init ablkuser_init( void )
027 {
028   int ret;
029   struct scatterlist sg[2];
030   char *encrypted, *decrypted;
031   struct crypto_ablkcipher *tfm;
032   struct ablkcipher_request *req;
033   struct crypt_result result;
034
035   printk("ablkuser_init\n");
036   encrypted = kzalloc(16, GFP_KERNEL);
037   if (!encrypted)
038     goto alloc_encrypted_failed;
039   decrypted = kzalloc(16, GFP_KERNEL);
040   if (!decrypted)
041     goto free_encrypted;
042
043   init_completion(&result.completion);
044
045   memcpy(encrypted, CIPHERTEXT, 16);
046
047   sg_init_table( sg, ARRAY_SIZE(sg) );
048   sg_set_buf( &sg[0], encrypted, 16 );
049   sg_set_buf( &sg[1], decrypted, 16 );
050
051   tfm = crypto_alloc_ablkcipher("ablk", 0, 0);
052   if (IS_ERR(tfm)) {
053     printk("crypto_alloc_ablkcipher failed %ld\n", (long)tfm);
054     goto free_decrypted;
055   }
056   ret = crypto_ablkcipher_setkey(tfm, key, strlen(key));
057   if (ret<0) {
058     printk("crypto_ablkcipher_setkey failed\n");
059     goto free_cipher;
060   }
061   /* Auftragsvergabe */
062   req = ablkcipher_request_alloc(tfm, GFP_KERNEL);
063   if (!req) {
064     printk("ablkcipher_request_alloc failed\n");
065     goto free_cipher;
066   }
067   ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
068       request_done, &result);
069   crypto_ablkcipher_clear_flags(tfm, ~0);
070   ablkcipher_request_set_crypt(req, &sg[0], &sg[1], 16, NULL);
071   ret = crypto_ablkcipher_decrypt(req);
072   if (ret == 0) {
073     request_done( &req->base, 0 );
074   } else if (ret!=-EINPROGRESS && ret!=-EBUSY) {
075     printk("crypte_ablkcipher_decrypt failed %d\n", ret);
076     goto free_all;
077   }
078   ret = wait_for_completion_interruptible(&result.completion);
079   if (ret || (ret = result.err)) {
080     printk("decryption failed %d\n", ret);
081     goto free_all;
082   }
083
084   print_hex_dump(KERN_INFO,"encr> ",DUMP_PREFIX_NONE,16,1,encrypted,16,1);
085   printk("\n");
086   print_hex_dump(KERN_INFO,"decr> ",DUMP_PREFIX_NONE,16,1,decrypted,16,1);
087   printk("\n");
088
089 free_all:
090   ablkcipher_request_free(req);
091 free_cipher:
092   crypto_free_ablkcipher(tfm);
093 free_decrypted:
094   kfree(decrypted);
095 free_encrypted:
096   kfree(encrypted);
097 alloc_encrypted_failed:
098   return -EIO; /* macht das Testen einfacher */
099 }
100
101 static void __exit ablkuser_exit( void ) {
102   return;
103 }
104
105 module_init(ablkuser_init);
106 module_exit(ablkuser_exit);
107
108 MODULE_LICENSE("GPL");

Das Ende der Auftragsbearbeitung lässt sich am besten mit Hilfe des Completion-Objekts verfolgen. Nachdem er die Aufträge dem Crypto-Subsystem zum Bearbeiten übergeben hat, ruft der User für jeden Auftrag einmal die Funktion »wait_for-completion()« auf (Abbildung 1). Die legt die Instanz schlafen, bis das korrespondierende »complete()« erfolgt, bis also die Callback-Funktion zum Ende des Auftrags aktiviert wurde.

Da sein Code neben dem Completion-Objekt typischerweise auch noch den Fehlerstatus eines Auftrags verwalten muss, deklariert der Programmierer für all diese Daten eine eigene Datenstruktur (Listing 1, Zeile 10) und instanziert sie für jeden Auftrag. Der Beispielcode reserviert zur Vereinfachung Speicher für diese Datenstruktur auf dem Stack (Zeile 33).

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 5 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook