/* basquiat's lovely winter riot */: a unique and beautiful snowflake in your heart's lovely winter riot

My first OpenBSD kernelbug

Mit ‘pf’, ‘pfsync’ und ‘CARP’ bietet OpenBSD eine vergleichsweise einfach zu administrierende, leistungsfähige Infrastruktur für das Failover redundant aufgesetzter Firewalls. Neben dem essentiellen Paketfilter ‘pf’ kümmert sich ’pfsync’ in diesem Kontext um das synchrone Statehandling der verschiedenen Nodes, um bei einem Wechsel des aktiven Firewallrechners im Cluster bestehende Netzwerkverbindungen aufrecht erhalten zu können. Der Einsatz des Common Address Redundancy Protocols CARP erledigt auf OSI-Schicht 2 und 3 dabei das eigentliche Procedere zur Hochverfügbarkeit, bei dem es klassischerweise zwischen MASTER- und BACKUP-Rollen auf Interface-Ebene unterscheidet. Eine detailreichere doch kurz gehaltene Übersicht bietet der Artikel “Firewall Failover with pfsync and CARP”.

Mit dem lange überfälligen Upgrade eines schon etwas in die Jahre gekommenen OpenBSD 3.7 Clusters auf Release 4.1 erhielt ich die einmalige Chance, meinen ersten OpenBSD-Kernelbug - wenn auch eher unfreiwillig in gewohnt unpassendster Situation - zu entdecken; das sequentielle Neuladen der Regeln des auf zwei Clusternodes verteilten Paketfilters führte nach wenigen Iterationen regelmäßig zum Absturz des Systems:

kernel: page fault trap, code = 0
Stopped at	pfsync_insert_net_state+0x472:	movl	0(%eax,%edx,4),%edx

Für Neulinge unter den digitalen Kammerjägern beschreibt die Kurzanleitung “How to debug kernel crashes” das Erstellen verwertbarer Bugreports für die Entwickler - in der Regel sollte man das fehlerhafte Verhalten jedoch zuerst unter Einsatz unmodifizierter GENERIC-Kernel reproduzieren können. Gesagt, getan; nun folgend das übersetzte Kochrezept.

Mit der Analyse des Trace-Outputs (im OpenBSD-Kerneldebugger ’ddb’ per ‘trace’ aufgerufen) kann die betreffende Funktion erkannt und im Quellcode schnell lokalisiert werden. Im Falle des gestorbenen Firewallnodes lässt folgende Ausgabe das Problem auf das Sourcefile ‘sys/net/if_pfsync.c’ zurückführen:

pfsync_insert_net_state(e34d4038,1,8,e34d4038) at pfsync_insert_net_state+0x472

pfsync_input(e3486a00,14,0,0,d0d1a034) at pfsync_input+0xa21
ipv4_input(e3486a00,d0d0e900,0,d08ab000,30) at ipv4_input+0x4f1
ipintr(d0640058,d30010,d08a0010,d08a0010,d08ab000) at ipintr+0x70
Bad frame pointer: 0xd08ace24
Erneut mit Debuginformationen kompiliert und disassembliert finden wir per ‘grep’ die fehlerhafte Funktion und addieren der dort angegebenen Speicheradresse den Offset aus unserem Trace-Output hinzu:
# grep “<pfsync_insert_net_state>” if_pfsync.dis
> 000002f4 <pfsync_insert_net_state>

Adam Riese addiert die Hexadezimalzahlen 0x2f4 + 0x472 zu 0x766 - genau in jener Zeile sollte sich innerhalb unseres disassemblierten Codes die Instruktion aus unserem Kerneltrap finden, und siehe da:

/usr/src/sys/net/if_pfsync.c:248
      756:       a1 b4 04 00 00          mov    0x4b4,%eax
      757:       R_386_32   pf_main_anchor
      75b:       66 c1 ca 08             ror    $0x8,%dx
      75f:       c1 ca 10                ror    $0x10,%edx
      762:       66 c1 ca 08             ror    $0x8,%dx
      766:       8b 14 90                mov    (%eax,%edx,4),%edx
      769:       89 55 ec                mov    %edx,0xffffffec(%ebp)
      76c:       e9 e5 fb ff ff          jmp    356 <pfsync_insert_net_state+0x62>
      771:       8d 76 00                lea    0x0(%esi),%esi

Damit haben wir die genaue Zeilenangabe des betreffenden Codeteils innerhalb der Funktion ‘pfsync_insert_net_state’ gewonnen, welche wir schon im Sourcefile ‘sys/net/if_pfsync.c’ festmachen konnten. Mit etwas Kontext sprechen also alle Indizien das folgende Konstrukt schuldig:

/*
* If the ruleset checksums match, it’s safe to associate the state
* with the rule of that number.
*/
if (sp->rule != htonl(-1) && sp->anchor == htonl(-1) && chksum_flag)
        r = pf_main_ruleset.rules[
            PF_RULESET_FILTER].active.ptr_array[ntohl(sp->rule)];
else
        r = &pf_default_rule;

Tiefer bewanderte Kerneldeveloper identifizieren hier eine Racecondition zwischen den Ruleset-Reloads beider Maschinen und stellen - keine 24 Stunden nach Meldung des Bugs - den ersten Patch zur Evaluation, der mittlerweile mit Revision 1.83 im CVS des MAIN-Branches enthalten ist - und auch hier erfolgreich unter Stress gesetzt wurde:

/*
* If the ruleset checksums match, it’s safe to associate the state
* with the rule of that number.
*/
if (sp->rule != htonl(-1) && sp->anchor == htonl(-1) && chksum_flag &&
    ntohl(sp->rule) <
    pf_main_ruleset.rules[PF_RULESET_FILTER].active.rcount)
        r = pf_main_ruleset.rules[
            PF_RULESET_FILTER].active.ptr_array[ntohl(sp->rule)];
else
        r = &pf_default_rule;

Das Fazit: Selbst durchaus unerfreuliche Vorkommnisse bieten bei Verfügbarkeit des Quellcodes das Potential, ungekannte Hintergründe zu verstehen und von Ihnen manchesmal Neues zu erlernen. Die Kommunikation mit den Entwicklern freier Software lässt darüber hinaus das gute Gefühl entstehen, selbst als einfacher Bote einer schlechten Nachricht beim Prozess der stetigen Verbesserung der quelloffenen Produkte positiv mitwirken zu können. Denn auch gerade davon lebt Open Source - den ausführlichen Bugreports dankbarer User.

1767 Klicks
  • Noch keine Kommentare
Umschließende Sterne heben ein Wort hervor (*wort*), per _wort_ kann ein Wort unterstrichen werden.
Die angegebene E-Mail-Adresse wird nicht dargestellt, sondern nur für eventuelle Benachrichtigungen verwendet.
BBCode-Formatierung erlaubt

Trackbacks / Pingbacks

  • Keine Trackbacks