Statische Codeanalyse für komplexe Systeme

Visao liest Ihren Quellcode und meldet Fehler, bevor das Programm läuft. Es findet Werte, die überlaufen, Indizes hinter dem Ende eines Arrays und Speicher, der nach der Freigabe noch benutzt wird.

Jeder Befund nennt die Regel, die Zeile und den Pfad, der dorthin führt. Visao ist der Teil von Midair für die statische Analyse.

visao · src/rx.cBeispiel
$visao compile_commands.json
Using toolchain clang (clang)
Parsing 1 / 3...
Solving …
src/rx.c:19:5:  error: The index is out of range
src/rx.c:26:12: error: Possible integer overflow
src/rx.c:33:19: error: Dangling pointer
✓ Finished successfully · 0.4 s

Was ist statische Codeanalyse?Code prüfen, ohne ihn auszuführen.

Statische Codeanalyse prüft den Quellcode, ohne das Programm auszuführen. Sie betrachtet jeden Pfad, den der Code nehmen kann, und meldet die Stellen, an denen ein Wert, ein Zeiger oder eine Ressource falsch werden kann.

Ein Test beantwortet eine andere Frage: Was passiert in den Läufen, die Sie tatsächlich ausführen? Ein Test sieht jeweils einen Pfad. Ein statisches Analysewerkzeug betrachtet viele Pfade zugleich, auch die seltenen, die kein Test erreicht.

Deshalb ergänzen sich beide. Die statische Analyse findet Fehler früh und ohne Hardware. Tests bestätigen, wie sich die Software in ihrer echten Umgebung verhält.

Unser Artikel (auf Englisch) vergleicht beide Verfahren ausführlich, mit Beispielen: Static and dynamic code analysis: the complete comparison.

Einen Befund lesen.Drei Markierungen in einer C-Datei.

Die Datei und die Befunde unten sind ein Beispiel. Wählen Sie eine Markierung am Rand, um den Befund und den Pfad dorthin zu sehen.

src/rx.cBeispiel
  1. /* rx.c: receive path of a serial driver */
  2. #include <stdint.h>
  3. #include <stdlib.h>
  4. #define RX_SIZE 64
  5. struct rx_ring {
  6. uint8_t data[RX_SIZE];
  7. uint16_t head;
  8. };
  9. void log_bytes(const uint8_t *p, uint16_t n);
  10. void rx_put(struct rx_ring *r, uint8_t byte)
  11. {
  12. if (r->head > RX_SIZE) {
  13. r->head = 0;
  14. }
  15. r->data[r->head] = byte;
  16. r->head++;
  17. }
  18. int32_t rx_timeout_us(const uint8_t *cfg)
  19. {
  20. uint16_t ms = (uint16_t)(cfg[0] | (cfg[1] << 8));
  21. return ms * 1000 * 1000;
  22. }
  23. void rx_drop(uint8_t *buf, uint16_t len, int keep_log)
  24. {
  25. free(buf);
  26. if (keep_log) {
  27. log_bytes(buf, len);
  28. }
  29. }

src/rx.c:19:5High

The index is out of range

Regel
array/index/out_of_range
MISRA C:2012
Rule 18.1 (Required)
Erkannt durch
Intervall-Domäne

Der Pfad dorthin

  1. 9head ist ein 16-Bit-Zähler, und data fasst 64 Byte. Der letzte gültige Index ist also 63.
  2. 16Das Zurücksetzen läuft nur, wenn head größer als 64 ist. Ist head genau 64, ist die Bedingung falsch, und der Code läuft weiter.
  3. 19Auf diesem Pfad schreibt der Code nach data[64], ein Byte hinter das Ende des Arrays.

src/rx.c:26:12High

Possible integer overflow

Regel
arithm/integer/overflow
MISRA C:2012
Rule 12.4 (Advisory)
Erkannt durch
Intervall-Domäne

Der Pfad dorthin

  1. 25ms entsteht aus zwei Konfigurationsbytes und kann daher jeden Wert von 0 bis 65535 haben.
  2. 26ms wird zu int erweitert und mit einer Million multipliziert. Das größte Ergebnis ist 65 535 000 000.
  3. 26Ein 32-Bit-int fasst höchstens 2 147 483 647. Jeder Wert von ms über 2147 läuft über.

src/rx.c:33:19Critical

Dangling pointer

Regel
mem/ptr/dangling
MISRA C:2012
Rule 1.3 (Required)
Erkannt durch
Zeiger- und Ressourcenzustands-Domäne

Der Pfad dorthin

  1. 31free gibt den Block frei, auf den buf zeigt. buf enthält weiter die alte Adresse.
  2. 32Nichts in der Funktion legt den Wert von keep_log fest. Beide Zweige bleiben also möglich.
  3. 33Ist keep_log gesetzt, erhält log_bytes den Zeiger buf und liest bereits freigegebenen Speicher.

Wie Visao ein Problem erkennt.Vier Arten, über ein Programm zu schließen.

Visao übersetzt jede Quelldatei in eine gemeinsame interne Darstellung und führt darauf mehrere Analysen aus. Wählen Sie eine aus, um zu sehen, was sie verfolgt.

Intervalle

Visao verfolgt für jeden Pfad den kleinsten und den größten Wert jeder Variablen. Geht ein Bereich über das hinaus, was der Typ oder das Array erlaubt, meldet Visao das.

Beispielms                 in [0, 65535]
ms * 1000 * 1000   in [0, 65 535 000 000]
int32_t max        2 147 483 647
der Bereich passt nicht: Überlauf

Oktagone

Visao verfolgt auch Beziehungen zwischen zwei Variablen, etwa i < n. Der Bereich von i allein ist oft zu weit für eine Entscheidung. Die Beziehung zeigt, ob ein Zugriff im Array bleibt.

Beispielfor (i = 0; i < n; i++) sum += in[i];

Intervall  i in [0, 65535]       keine Entscheidung
Oktagon    i - n <= -1           in[i] bleibt in in[n]

Zeiger

Visao verfolgt, worauf jeder Zeiger zeigen kann, und den Zustand jeder Ressource: belegt, freigegeben, geöffnet, geschlossen. Ein Lesen über einen freigegebenen Zeiger, eine zweite Freigabe oder ein Handle, das offen bleibt, ist ein Befund.

BeispielZeile 29   buf -> Block A        gültig
Zeile 31   free(buf)             A freigegeben
Zeile 33   log_bytes(buf, len)   A nach der Freigabe gelesen

SMT

Wenn die Analysen oben Unknown liefern, kann Visao den Solver Z3 oder CVC5 fragen, ob alle Bedingungen eines Pfads zugleich gelten können. Wenn nicht, gibt es den Pfad nicht, und Visao meldet dort nichts.

BeispielPfad zum Zugriff:   x > 10  und  2 * x < 15
Solver (Z3):        unsat
der Pfad ist unmöglich: kein Befund

Wenn Ihr Code seine Annahmen als Verträge im ACSL-Stil festhält, prüft Visao auch diese.

Ein Befund, vier Formate.Für Menschen, für CI und für Skripte.

Derselbe Befund aus rx.c, so wie ihn jedes Berichtsformat schreibt. Die Beispiele sind gekürzt.

Die Standardausgabe in der Konsole. Jeder Befund zeigt Datei, Zeile und Spalte, die Meldung und die Quellzeile.

$ visao src/rx.csrc/rx.c:19:5: error: The index is out of range
        r->data[r->head] = byte;
        ^
✓ Finished successfully · 0.4 s

Für GitHub Code Scanning, Jenkins Warnings NG und Azure DevOps. Ein Fingerabdruck an jedem Ergebnis hält den Befund stabil, wenn sich Zeilen verschieben.

$ visao --report-format sarif --report-output visao.sarif ...{
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
  "version": "2.1.0",
  "runs": [{
    "tool": { "driver": { "name": "Visao", "rules": [ ... ] } },
    "results": [{
      "ruleId": "array/index/out_of_range",
      "level": "error",
      "message": { "text": "The index is out of range" },
      "locations": [{ "physicalLocation": {
        "artifactLocation": { "uri": "src/rx.c" },
        "region": { "startLine": 19, "startColumn": 5 } } }],
      "partialFingerprints": { "visao/v1": "..." }
    }]
  }]
}

Ein kompaktes Array von Befunden für jq und Ihre eigenen Skripte.

$ visao --report-format json src/rx.c{
  "tool": "visao",
  "findings": [{
    "ruleId": "array/index/out_of_range",
    "helpUri": "https://defectdb.org/array/index/out_of_range",
    "severity": "High",
    "level": "error",
    "message": "The index is out of range",
    "file": "src/rx.c",
    "line": 19,
    "column": 5
  }]
}

Das SAST-Berichtsschema von GitLab. GitLab zeigt es im Sicherheits-Widget des Merge Requests.

$ visao --report-format gitlab --report-output gl-sast-report.json ...{
  "version": "15.0.6",
  "scan": { "type": "sast", "scanner": { "id": "visao", "name": "Visao" } },
  "vulnerabilities": [{
    "id": "array/index/out_of_range:src/rx.c:19",
    "category": "sast",
    "name": "array/index/out_of_range",
    "description": "The index is out of range",
    "severity": "High",
    "location": { "file": "src/rx.c", "start_line": 19 },
    "identifiers": [
      { "type": "visao_rule", "value": "array/index/out_of_range" },
      { "type": "cwe", "value": "119" }
    ]
  }]
}

Nur neue Befunde stoppen den Merge

Speichern Sie die Befunde des Hauptzweigs als SARIF-Baseline. In einem Merge Request entfernt --baseline alle Befunde, die schon darin stehen. Danach endet --fail-on high nur dann mit Code 1, wenn ein neuer Befund High oder höher ist.

Fragen.Kurze Antworten.

Was ist statische Codeanalyse?

Statische Codeanalyse prüft den Quellcode, ohne das Programm auszuführen. Sie betrachtet jeden Pfad, den der Code nehmen kann. Sie meldet Fehler wie Überläufe, Indizes hinter dem Ende eines Arrays und Speicher, der nach der Freigabe benutzt wird.

Was unterscheidet sie vom Testen?

Ein Test führt das Programm aus und sieht die Pfade, die dabei laufen. Die statische Analyse sieht viele Pfade auf einmal, auch seltene, beobachtet das Programm aber nicht in seiner echten Umgebung. Die meisten Teams nutzen beides.

Welche Sprachen analysiert Visao?

Neunzehn Sprachen, darunter C, C++, Rust, Go, Python, Java und Assembler. Für C und C++ liest Visao Ihre compile_commands.json und nutzt so dieselben Flags und Include-Pfade wie Ihr Build.

Prüft Visao MISRA C?

Ja. Mit --standard misra-c-2012 trägt jeder passende Befund die Regelnummer aus MISRA C:2012. Der Lauf endet mit einer Übersicht der geprüften und nicht geprüften Regeln.

Wie passt Visao in die CI?

Visao ist ein einziger Befehl. Es schreibt SARIF 2.1.0, JSON oder einen GitLab-SAST-Bericht, und mit --fail-on lässt der Exit-Code den Schritt fehlschlagen. Eine Baseline beschränkt diese Prüfung auf neue Befunde.

Verlässt unser Code unsere Rechner?

Nein. Visao läuft auf Ihrem Rechner, sendet nichts nach außen und nutzt keinen Dienst eines Dritten.

Beginnen Sie mit einem Repository.Sehen Sie, was Visao in Ihrem Code findet.

Vergleichen Sie die Pläne auf der Preisseite. Die Preise sind monatlich, in Euro.

Auf die Warteliste