Prüfstand für Testwerkzeuge: Codeanalyse im Praxiseinsatz

Die statische Codeanalyse hilft beim Auffinden einiger kritischer Fehler. Einige der Werkzeuge zeigen im Praxistests ihr Können.

Lesezeit: 7 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 8 Beiträge
Von

Im Jahr 2005 hat Microsoft eine Ergänzung zum Softwareentwicklungsprozess vorgestellt: den Secure Development Lifecycle (SDLC). Er war der neue und zentrale Baustein für die Entwicklung von Windows Vista, das planmäßig sicherste Windows-Betriebssystem bis dahin, nachdem Microsoft viel schlechte Presse, Spott und Hohn für Windows XP und den Umgang mit Sicherheitsproblemen generell geerntet hatte – auch für Windows NT. Nicht zuletzt hatten Behörden gedroht, zu Linux zu wechseln, sollte es in zukünftigen Versionen nicht besser um die Sicherheit bestellt sein.

Microsoft hat 2008 das gesamte Konzept, die Hintergrunddokumentation und unterstützende Software für den Prozess veröffentlicht. Bis heute entwickelt das Unternehmen nach diesem Prozess und aktualisiert ihn regelmäßig unter anderem für die Anwendung in agilen Entwicklungsprozessen.

Nach nunmehr fast 15 Jahren führen große Teile der Softwareindustrie langsam sichere Entwicklungsprozesse ein. Grund dafür sind weniger die erhöhte Sicherheit und die verringerten Folgekosten als vielmehr die Vorschriften in den Ausschreibungsbedingungen zahlreicher großer Hersteller der Automobil-, Aeronautik-, Medizin- und Finanzbranche.

In den 17 vorgeschriebenen und einigen optionalen Prozessschritten enthalten ist der Punkt statische Quellcodeanalyse (Static Code Analysis, SCA). Sie ist eine der wenigen Aufgaben, die sich zum Großteil softwaretechnisch lösen lassen. Den Tools haftet jedoch das Vorurteil an, teuer zu sein, viel Konfigurationsarbeit mit sich zu bringen und dabei wenig verwertbare Ergebnisse zu liefern. Einige Teams haben dagegen die Erwartung, durch den Einsatz von SCA nahezu keine Sicherheitsprobleme in der Software mehr zu haben. Zeit für einen Realitätscheck.

Es gibt über 25 Softwareprodukte für die statische Quellcodeanalyse, die sich deutlich voneinander unterscheiden: von der Art und Weise, wie sie vorgehen, nach welchen Fehlertypen sie suchen können und welche Programmiersprachen sie unterstützen. Und viele Hersteller scheuen den Vergleich.

Dieser Artikel möchte beispielhaft einige Produkte in vergleichbarer Form untersuchen. Die zu analysierende Programmiersprache ist C beziehungsweise C++. Der Autor hat 20 Firmen angeschrieben, ob sie an einem Vergleichstest auf heise Developer teilnehmen möchten. Manche bestanden darauf, die Tests selbst vorzunehmen, andere wollten den Test nur für Java-Code. Weitere Firmen haben für den deutschen Markt schlichtweg keinen Ansprechpartner. Schließlich blieben nur vier Anbieter übrig, die bereit waren, sich unabhängig unter die Haube schauen zu lassen. Daraus ergibt sich der Testreigen aus MathWorks Polyspace Bug Finder/Code Prover, Parasoft C/C++test, Perforce Klocwork und Viva64 PVS Studio. Den Kreis der kommerziellen Tools ergänzen die quelloffenen Programme cppcheck und clang-analyzer.

Der ursprünglich Plan sah vor, den Werkzeugen komplexe Programme mit eingebauten Fehlern zum Prüfen zu geben, um die Erkennungsquote zu bewerten. Die Realität brachte jedoch schnell zwei Erkenntnisse: Fast alle Werkzeuge benötigen umfangreiche Anpassungen der Regeln, damit Tester nicht in Meldungen ertrinken, und jedes Tool findet unterschiedliche Sicherheitsprobleme, die die jeweils anderen übersehen. Mit dem Aufbau lässt sich ein Vergleich kaum fair gestalten.

Daher entschloss sich der Autor, stattdessen einfachen, fehlerbehafteten Code zu erstellen und aufzuzeigen, was die jeweiligen Programme finden. Außerdem hielt er Rücksprache mit den Herstellern, um sicherzugehen, dass das Übersehen von Fehlern nicht in einer Fehlkonfiguration begründet ist.

Die im Artikel vorgestellten Programmtexte sind aus Gründen der besseren Lesbarkeit teilweise vereinfacht und verzichten auf Includes.

Im ersten Schritt arbeiten alle Programme ähnlich: Sie klinken sich in den Build-Prozess ein und lernen dadurch die gesetzten Optionen für die Compiler, Includes und deren Speicherort sowie die beim Link-Vorgang genutzten Bibliotheken. Danach gehen die Werkzeuge allerdings recht unterschiedlich vor, um Probleme zu identifizieren.

PVS Studio gehört zu den günstigsten Angeboten am Markt. Es ist für Linux, macOS und Windows verfügbar und analysiert Code in den Programmiersprachen C, C++, C# und Java.

Für das Tool existiert eine Visual Studio Extension, mit der Entwickler ihren Code aus dem Editor heraus prüfen können. Im Gegensatz zu anderen Werkzeugen bringt PVS Studio keine grafische Bedienoberfläche mit, sondern setzt auf Kommandozeilenbefehle zum Ausführen und Steuern. Die HTML-Ausgabe der Kommandozeilenprogramme ist allerdings durchaus übersichtlich.

In der Enterprise Variante von PVS-Studio (ab 10 Entwicklern) ist auch eine Integration in SonarQube enthalten.

Im Vergleich zu den anderen Produkten hat es wenige Sicherheitsprobleme identifiziert, war aber das einzige Programm, das einen Buffer Overflow im memcpy() in folgendem Codeschnipsel gefunden hat:

int main(int argc, char *argv[]) {
  char buf[128];
  // atoi() gibt auch Minuswerte zurück:
  int user_len = atoi(argv[2]); 
  // Prüfung wird durch Minuszahlen umgangen:
  int buf_size = sizeof(buf);

  if (user_len > buf_size) { 
    printf("Attempted Overflow Detected\n");
    return -1;
  }

  // ein Minuszeichen wird als gesetztes
  // höchstwertiges Bit interpretiert:
  memcpy(buf, argv[1], user_len); 
  return 0;
}

PVS Studio lässt sich folgendermaßen verwenden:

$ pvs-studio-analyzer trace -- make
$ pvs-studio-analyzer analyze -o \\
  pvs-studio.out \\
  -l /home/testuser/.config/PVS-Studio/PVS-Studio.lic
$ plog-converter -a GA:1,2 -t tasklist -o \\
  pvs-studio.txt  pvs-studio.out

Die generierte Datei pvs-studio.txt enthält folgende Erkenntnisse:

/opt/vulntest/strcpy.c 11 warn V618 \\
  It's dangerous to call the 'printf' function \\
  in such a manner, as the line being passed \\ 
  could contain format specification. The example \\ 
  of the safe code: printf("%s", str);

/opt/vulntest/strcpy2.c 13 warn V618 \\
  It's dangerous to call the 'printf' function \\
  in such a manner, as the line being passed \\ 
  could contain format specification. The example \\
  of the safe code: printf("%s", str);

/opt/vulntest/use_after_free.c 29 err V595 \\
  The 'container.foo.b' pointer was utilized \\
  before it was verified against nullptr. \\
  Check lines: 29, 33.

Parasoft ist einer der älteren Player im Markt und einer der wenigen, die in Europa entwickeln. Das Tool ist ausschließlich auf C und C++ ausgelegt – wobei es auch getrennte Varianten für .Net und Java gibt. Es läuft auf Linux und Windows und besitzt ein Plug-in für die Eclipse-Entwicklungsumgebung. Darüber hinaus unterstützt es die Einbindung in Continuous-Integration-Tools (CI) wie Jenkins oder Bamboo.

Wie alle anderen komplexeren Werkzeuge zeigt es an, welche Programmteile es untersucht hat (Coverage). Die Ergebnisse der Tests waren allerdings enttäuschend. Das war nach Auskunft des Herstellers der Tatsache geschuldet, dass die Tests mehrere bekannte Fehler in der Software trafen, die erst in der nächsten Version bereinigt werden sollten.

Dazu gehört folgendes Beispiel, das aus der NIST-Testsuite "Juliet" für statische Quellcodeprüfer stammt und einen "double free"-Bug enthält – in einer Form, die Testern bei der manuellen Inspektion eines komplexen Programms leicht übersehen können:

namespace CWE415_Double_Free__no_copy_const_01 {
  class BadClass {
    public:
      BadClass(const char *data) {
        if (data) {
          this->data = new char[strlen(data) + 1];
          strcpy(this->data, data);
        } else {
          this->data = new char[1];
          *(this->data) = '\0';
        }
      }

      ~BadClass() { delete[] data; }

      BadClass &operator=(const BadClass &badClassObject) {
        if (&badClassObject != this) {
          this->data = 
            new char[strlen(badClassObject.data) + 1];
          strcpy(this->data, badClassObject.data);
        }
        return *this;
      }

      void printData() { printf("%s\n", data); }

    private:
      char *data;
  };

  void bad() {
    BadClass badClassObject("One");
    /* FLAW: There is no copy constructor in the class.
     * This will cause a double free in the destructor */
    BadClass badClassObjectCopy(badClassObject);
    badClassObjectCopy.printData();
  }
}  // namespace CWE415_Double_Free__no_copy_const_01

using namespace CWE415_Double_Free__no_copy_const_01;

int main(int argc, char *argv[]) {
  bad();
  return 0;
}

Die Software lässt sich im Vergleich mit den anderen mit den wenigsten Handgriffen starten. Grundlage ist das in der Desktop-Oberfläche recht komfortable Generieren einer angepassten Policy.

$ cpptestcli \\ 
  -config file:///home/testuser/parasoft.properties \\
  -compiler gcc_7-64 -report ./ -trace make

Die Policy Konfiguration in der c/c++test Deskop Oberfläche (Abb. 1).

Wie Parasofts Tool ist auch der Polyspace Bug Finder Desktop ausschließlich auf C/C++ ausgelegt und läuft auf Linux, macOS und Windows. Jedes Tool hat seine Eigenheiten und bei MathWorks ist es das Aufsplitten in viele einzelne Produkte.

Daher existieren zwei sich ergänzende Produkte: der Bug Finder und der Code Prover. Ersterer sucht nach diversen Problemen im Code, Letzterer dagegen versucht durch eine Verifizierung des Datenfluss alle Probleme zu identifizieren die die Robustheit eines Programms in Mitleidenschaft ziehen könnte – darunter Buffer Overflows. Der Grund dafür ist die unterschiedliche Art und Weise wie die Programme unter der Haube arbeiten.

Die Frage, ob Nutzer von Bug Finder überhaupt Code Prover benötigen lässt sich nach den für diesen Artikel vorgenommen Tests klar mit "Ja" beantworten. Der Bug Finder fand im Gegensatz zum Code Prover keinen einzigen Buffer Overflow. Dabei zählen generische Meldungen wie "hier wird strcpy benutzt" nicht als Erkennen, da sich die gefährlichen Funktionen auch fehlerfrei verwenden lassen.

Für Anwender ist die Fehlersuche dadurch jedoch aufwendiger, da sie die beiden Tools unabhängig anwenden müssen. Beide Desktop-Produkte enthalten ein Eclipse-Plug-in.

Bug Finder und Code Prover haben jeweils weitere individuelle Produkte: die Access- und die Server-Komponenten. Der Server ist jeweils ein abgespeckter Bug Finder/Code Prover, der eigenständig beispielsweise innerhalb eines Jenkins oder Jira laufen soll. Das Access-Produkt ist im Wesentlichen ein Webserver, der es ermöglicht, die Ergebnisse der Tools ohne eine separate Lizenz für den Desktop interaktiv zu betrachten und zu analysieren.

Eine weitere Eigenheit dieser Werkzeuge ist, dass einige Prüfoptionen standardmäßig deaktiviert sind, während bei den meisten Produkten äußerst viel aktiviert ist. Leider gehören zu den fehlenden Defaults Sicherheitsprüfungen, Kryptografietests und Taint Data Checker. Dadurch müssen Tester noch genauer auf die Konfiguration achten als beim Wettbewerb.

In den für den Artikel durchgeführten Tests kamen in der Summe ihrer Testläufe der Bug Finder und der Code Prover gemeinsam auf die beste Quote bei der Fehlersuche, hängten allerdings in keinem Bereich alle Mitbewerber ab.

Der Polyspace Code Prover fand als einziger folgenden Buffer Overflow:

int main(int argc, char *argv[]) {
  char buf[16] = "";
  char *in = argv[1], *out = buf;

  if (argc > 1) {
    // Kein Längencheck - das ist eigentlich 
    // ein strcpy(out, in):
    while (*in != 0) 
      *out++ = *in++;
    *out = 0;
  }
  (void)printf("%s\n", buf);
  
  return 0;
}

Der Start des Bug Finder geschieht nach dem Einrichten einer Prüfkonfiguration im Desktop-Tool folgendermaßen:

$ polyspace-configure -allow-build-error \\
  -allow-overwrite -prog "NAME_DES_PROJEKTES" \\
  -output-options-file myOptionsFile.txt make

$ polyspace-bug-finder -options-file \\
  myOptionsFile.txt -compiler gnu7.x \\ 
  -checkers-selection-file \\
  ~/polyspace_checks.xml \\ 
  -report-output-format HTML \\ 
  -report-template \\ 
  $POLYSPACE/toolbox/polyspace/psrptgen/templates/\\ 
  bug_finder/CodingStandards.rpt

Der Polyspace Desktop auf Linux sieht etwas altbacken aus, erfüllt aber seinen Zweck (Abb. 2).

Als letztes und umfangreichtes Produkt steht Klocwork auf dem Prüfstand. Ehemals von Rogue Wave entwickelt, hat Perforce es vor Kurzem übernommen. Zusammen mit dem ursprünglich von PRQA stammenden Helix QAC hat der Hersteller damit zwei Produkte für statische Softwareanalysen im Angebot. Wie lange Perforce es sich leisten möchte und kann, zwei ähnliche Produkte zu betreuen und zu erweitern, wird sich zeigen – laut dem Hersteller richten sich die Anwendungen an unterschiedliche Zielgruppen und sollen daher beide weiterhin betreut werden.

Während sich alle anderen Tools ohne größere Hindernisse starten lassen, erwartet Klocwork speziell unterstütze Linux-Varianten, damit die Serverkomponente richtig funktioniert.

Klocwork beherrscht neben C/C++ die Programmiersprachen C# und Java. Es lässt sich in die Eclipse IDE, in Visual Studio und in IntelliJ IDEA einbinden. Serverkomponenten existieren für Linux und Windows und das Kommandozeilenprogramm für den Client zusätzlich für macOS, Solaris und AIX, wenn auch für Letzteres der Link zum Download fehlt. Im CI-Prozess lässt es sich in Jenkins und TeamCity einbinden.

Positiv fiel bei Klocwork auf dass man eigene Regeln als C++-Modul aufsetzen kann, um spezifische Probleme zu detektieren.

Klocwork war das einzige Produkt, das fest einprogrammierte Passworttests identifizierte wie in folgenden Codeausschnitten:

 WebRequestForm admin_request = \\
{"admin", "TopSecret", 5000.0};

beziehungsweise

if (strcmp(password, "DEADBEEF") == 0)
...

Neben dem Polyspace Bug Finder war es das einzige Tool, das eine schwache Verschlüsselung erkannte:

EVP_EncryptInit_ex(ctx, EVP_des_ecb(), NULL, key, iv))

Allerdings ist die Meldung dafür fehlerhaft:

function 'encrypt' is a weak hash function, \\ 
  use stronger hashing. Weak hash functions \\ 
  can be easily be deciphered. Use a \\ 
  stronger cryptographic function.

Es handelt sich jedoch um eine Verschlüsselungs- und nicht um eine Hashfunktion.

Als einziges Tool fand es dagegen weder offensichtliche Buffer Overflows noch Format-String-Schwächen wie in folgendem einfachen Beispiel:

int main(int argc, char *argv[]) {
  char buf[16] = "";
  char *in = argv[1], *out = buf;

  if (argc > 1)
    strcpy(out, in);            // buffer overflow

  (void)printf(buf);            // format string
  return 0;
}

Der Einsatz von Klocwork ist etwas aufwendiger: Zunächst müssen Tester ein Build-Projekt anlegen und ein ein geeignetes Regelwerk konfigurieren:

$ KW_SERVER_URL="http://localhost:8080"
$ kwadmin --url $KW_SERVER_URL create-project "PROJEKT_NAME"
$ kwdeploy sync --url $KW_SERVER_URL
$ kwadmin --url $KW_SERVER_URL import-config \\
"PROJEKT_NAME" ~/klocwork_rules.pconf

Anschließend lässt sich jeder neu zu prüfende Build-Prozess wie folgt starten:

$ kwinject -o kwinject.out make
$ kwbuildproject --url \\
$KW_SERVER_URL/"PROJEKT_NAME" \\
--tables-directory kwtables kwinject.out
$ kwadmin --url $KW_SERVER_URL load \\
"PROJECT_NAME" kwtables

Die Weboberfläche von Klocwork ist modern und lässt sich schnell und gut bedienen (Abb. 3).

Folgende Tabelle zeigt die Funktionsweise, den Preis und die Anbindung der getesteten Werkzeuge.

Firma Viva64 Parasoft MathWorks Perforce
Software PVS Studio C/C++test Polyspace Bug Finder & Code Prover (1) Klocwork
Version 7.01.30939.1042 10.4.2 R2019a 19.01
C/C++ ja ja ja ja
C# ja separat nein ja
Java ja separat nein ja
Plattformen Client Linux, Windows, macOS Linux, Windows Linux, Windows, macOS Linux, Windows, macOS, Solaris, AIX
Plattformen Server - Linux, Windows Linux, Windows, macOS (2) Linux, Windows
IDE Visual Studio Eclipse, eigener Desktop Eclipse, eigener Desktop Eclipse, Visual Studio, IntelliJ IDEA
Continuous Integration - Jenkins, Bamboo Jenkins Jenkins, TeamCity
Bugtracking SonarQube (4) Jira Jira -
ca. Preis (5) 20.000 Euro 30.000 Euro keine Angaben 100.000 Euro
(1) zwei Produkte
(2) zusätzliche Produkte
(3) durch die Kommandozeilenprogramme ist eine Integration in fast alles möglich. Hier aufgelistet sind die Produkte, die sich laut Hersteller als fertige Konfigurationen angewendet werden lassen.
(4) Enterprise Variante
(5) ca. 20 Entwickler

Den Vergleich rundet ein Blick auf die Open-Source-Tools cppcheck und clang-analyzer ab. cppcheck findet lediglich einen "Use After Free"-Fehler, den alle anderen Werkzeuge ebenfalls gefunden haben:

$ cppcheck --force --quiet --inconclusive -I. *.c *.cpp

[use_after_free.c:34]: (error) \\
Dereferencing 'b' after it is \\
deallocated / released

Der clang-analyzer befindet sich in Linux-Distributionen meist im clang-tools-Paket. Er lässt sich folgendermaßen nutzen:

$ scan-build -analyze-headers -k make

scan-build: Using '/usr/lib/llvm-8/bin/clang' \\
  for static analysis

[...]

scan-build: 8 bugs found.
scan-build: Run 'scan-view \\
  /tmp/scan-build-2019-09-13-194623-19774-1' \\
  to examine bug reports.
$ scan-view /tmp/scan-build-2019-09-13-194623-19774-1
Starting scan-view at: http://127.0.0.1:8181
  Use Ctrl-C to exit.

Tester können in einem lokalen Webserver Ergebnisse ansehen (Abb. 4).

Der clang-analyzer bringt ein hübscheres Äußeres als cppcheck mit, konnte aber nur einen zusätzlichen Fehler erkennen – einen Double Free Bug. Im Vergleich haben die kommerziellen Tools das Doppelte und Dreifache der Fehler gefunden, sodass die Open-Source-Werkzeuge bislang bei weitem nicht mithalten können.

Zwei Fehlerklassen fand keines der getesteten Werkzeuge: das unbeabsichtigte Weiterleiten von Datei-Deskriptoren (Filedescriptor Leakage) und Type Confusion.

Code mit der ersten Fehlerklasse vererbt einen Datei-Deskriptor an einen Unterprozess, der unter die Kontrolle eines Angreifers kommen und dann auf den Deskriptor zugreifen kann. Dieses Szenario ist jedoch recht selten.

Bei Type Confusion handelt es sich um ein fehlerhaftes Typecasting einer Klasse wie in

mutterObjekt *mutter =
static_cast<mutterObjekt*> kind;

Es kann in Folge zu Pufferüberlaufen und anderen Fehlern führen. Es gehört zu den häufigsten kritischen Sicherheitslücken in modernen C++-Anwendungen wie Webbrowsern. Daher ist es besonders schwerwiegend, dass alle Programme diese Art von Bugs nicht entdecken. Die einzige dem Autor bekannte Lösung, hiernach effizient zu suchen, ist über Fuzzing in Verbindung mit speziellen Sanitizern mit LLVM.

Software zur statischen Quellcodeanalyse hilft fraglos beim Auffinden von Fehlern. Allerdings gehen die wichtigen Feststellungen in einer Flut unwichtiger Meldungen unter, sodass Tester die Konfiguration für alle Produkte genau anpassen müssen.

Wer strengen Auflagen wie dem MISRA-Standard folgt beziehungsweise folgen muss, hat den Vorteil, dass alle Tools die Standards in Form von Regelwerken unterstützen. Unter dem Strich bleibt zu sagen, dass derjenige, der mehr Geld ausgibt, auch ein leistungsstärkeres Tool erhält – hinsichtlich der Prüfqualität oder dem Funktionsumfang.

Alle Programme beherrschen nur Englisch als in Europa relevante Ausgabesprache – der Vollständigkeit halber sei erwähnt, dass einige zusätzlich asiatische Sprachen anbieten. Abschließend lässt sich festhalten, dass der Sourcecode nach den von den Analysewerkzeugen vorgeschlagenen Änderungen nicht fehlerfrei ist.

Die für den Artikel verwendeten, einfachen Tests sind auf GitHub verfügbar. Eine Umfangeiche Testsuite findet sich in der NIST-Bibliothek.

Marc Heuse
arbeitet seit über 20 Jahren ausschließlich in der IT-Sicherheit und hat viele bekannte Programme wie hydra, thc-ipv6, afl++, SuSEfirewall entwickelt. Er arbeitet als unabhängiger Sicherheitsberater und Pentester in Berlin.
(rme)