Flow -- Eine Datenfluss-Beschreibungssprache für

Werbung
Flow — Eine Datenfluss-Beschreibungssprache
für objektorientierte Programme
Autoren:
Marek Jawurek
Stefan Mandel
IESE-Report Nr. 086.08/D
Version 1.0
November 2008
Eine Publikation des Fraunhofer IESE
Das Fraunhofer IESE ist ein Institut der
Fraunhofer-Gesellschaft. Das Institut
transferiert innovative SoftwareEntwicklungstechniken, -Methoden und
-Werkzeuge in die industrielle Praxis. Es
hilft Unternehmen, bedarfsgerechte
Software-Kompetenzen aufzubauen und
eine wettbewerbsfähige Marktposition
zu erlangen.
Das Fraunhofer IESE steht unter der Leitung
von
Prof. Dr. Dieter Rombach (geschäftsführend)
Prof. Dr. Peter Liggesmeyer
Fraunhofer-Platz 1
67663 Kaiserslautern
Kurzfassung
Der Bericht beschreibt Flow, eine Sprache zur Spezifikation von Datenflüssen in
objektorientierten Programmen. Flow wurde im Rahmen des Projekts SecFlow
entwickelt, dessen Zielsetzung es ist, sicherheitskritische Datenflüsse in
Quellcode mittels werkzeuggestützter Analyse zu ermitteln.
In SecFlow werden Flow-Spezifikationen zum einen genutzt, um
sicherheitsrelevante Datenquellen und Datensenken zu bezeichnen. Zum
anderen dienen sie dazu, das Datenflussverhalten von Software-Komponenten
zu charakterisieren, deren Inneres jenseits des Analysehorizonts liegt oder deren
Quellcode für Analysen nicht zur Verfügung steht: Eine geeignete FlowCharakterisierung ermöglicht es, Datenflüsse auch durch opaque SoftwareKomponenten hindurch weiterzuverfolgen.
Das Verbundprojekt »SecFlow: Automatische Erkennung sicherheitskritischer Datenflüsse in
Quellcode« wird vom Bundesministerium für Bildung und Forschung (BMBF) unter dem
Förderkennzeichen 01ISF09C gefördert.
Schlagwörter:
Datenfluss, Quellcode-Analyse, Java, C#, IT-Sicherheit, SecFlow, BMBF
Keywords:
data flow, source code analysis, program analysis, computer software –
evaluation, computer software – quality control, Java programming language,
C# programming language, information security, IT security, SecFlow, BMBF
Copyright © Fraunhofer IESE 2008
v
Inhaltsverzeichnis
Kurzfassung
Copyright © Fraunhofer IESE 2008
v
Inhaltsverzeichnis
vii
Verzeichnis der Flow-Beispiele
ix
Tabellenverzeichnis
xi
1
Datenflüsse und Datenfluss-Spezifikationen
1
1.1
Datenflüsse
1
1.2
Datenflussanalysen
1
1.3
Datenfluss-Spezifikationen
2
1.4
Die Datenfluss-Beschreibungssprache Flow
2
2
Spezifizieren in Flow
3
3
Grundstruktur einer Flow Summary
5
3.1
Language Hint
5
3.2
Imports
5
3.3
Summary-Definitionen
6
3.4
Attribute
6
3.5
(Statische) Initialisierer
7
3.6
Konstruktoren und Methoden
9
4
Flow-Anweisungen
4.1
Eingangs- und Ausgangsbezeichner einer Flow-Anweisung 11
4.2
Binden von Quellen und Senken
12
4.3
Flows
13
4.4
Funktionale Ausdrücke
14
11
vii
viii
4.5
Pipes
16
4.6
Objekt-Instanziierung
16
4.7
Daten-Generierung/Konsumierung
17
5
Fazit
19
Anhang — Anwendungsbeispiele
21
Abkürzungsverzeichnis
25
Quellenverzeichnis
27
Copyright © Fraunhofer IESE 2008
Verzeichnis der Flow-Beispiele
Beispiel 1
Verwendung von Imports .....................................................6
Beispiel 2
Summary-Definition..............................................................6
Beispiel 3
Verwendung von Attributen .................................................7
Beispiel 4
Flow-Initialisierer...................................................................8
Beispiel 5
Datenfluss-Spezifikation für Methoden und Konstruktoren ...9
Beispiel 6
Flow-Bezeichner .................................................................11
Beispiel 7
Binden von Quellen und Senken .........................................12
Beispiel 8
Propagieren gebundener Quellen und Senken.....................12
Beispiel 9
Spezifizieren von Flüssen.....................................................14
Beispiel 10
Verwendung von Pipes .......................................................16
Beispiel 11
Objektinstanziierung...........................................................16
Beispiel 12
Spezifizieren eines Datengenerators ....................................17
Beispiel 13
Spezifizieren eines Datenkonsumenten ...............................17
Beispiel 14
Flow-Spezifikation der Klasse java.lang.String......................21
Beispiel 15
Flow-Spezifikation der Klasse.java.net.URLEncoder..............22
Beispiel 16
Flow-Spezifikation der Klasse.java.net.URLDecoder .............23
Copyright © Fraunhofer IESE 2008
ix
Tabellenverzeichnis
Tabelle 1
Copyright © Fraunhofer IESE 2008
Vordefinierte Flow-Funktionen............................................14
xi
Datenflüsse und Datenfluss-Spezifikationen
1
Datenflüsse und Datenfluss-Spezifikationen
Das Datenflussverhalten eines Programms ist ein Software-Merkmal, das
wesentlich dazu beiträgt, die Bedeutung der Programmlogik zu erschließen. Die
Charakterisierung von Datenflüssen ist daher ein wichtiger Schritt bei der
Programmanalyse.
1.1
Datenflüsse
Computerprogramme lesen Eingabedaten und transformieren diese Schritt für
Schritt in die gesuchten Ausgabedaten. Das gilt nicht nur für Anwendungen im
Bereich der konventionellen Informationsverarbeitung, sondern auch für
eingebettete, reaktive und echtzeitorientierte Software, die Sensor-Eingaben
entgegennimmt, um daraus geeignete Steuerimpulse für eine Aktorik
abzuleiten.
Bei jedem Verarbeitungsschritt werden die vorliegenden Daten weitergereicht,
von Anweisung zu Anweisung, Funktion zu Funktion und Modul zu Modul. Die
Eingabedaten fließen also von den möglichen Eingabeschnittstellen eines
Programms über verzweigte, oft schwer zu überblickende Wege bis hin zu den
Ausgabeschnittstellen. Einen solchen potentiellen Weg durch das Programm
nennen wir einen Datenfluss.
1.2
Datenflussanalysen
Auf ihrem Weg durch das Programm unterliegen die Daten vielfältigen
Veränderungen. Sie werden umformatiert, zerlegt und neu kombiniert. Dies
erschwert es, den genauen Datenfluss eines konkreten Datenobjekts zu
verfolgen. Datenflüsse neigen zur Verästelung: Einige Zweige erweisen sich als
Sackgassen, da die entsprechenden Datenfragmente gelöscht werden, während
auf anderen Datenfluss-Zweigen Bruchteile der Ausgangsdaten weiter
propagiert werden und so Einfluss auf den weiteren Verlauf der Berechnung
nehmen.
Um die Wirkungsweise eines Programms zu verstehen ist es wichtig, alle
Datenflüsse des Programms zu überblicken. Ein tiefes Datenflussverständnis ist
zum Beispiel erforderlich, um aus Quellprogrammen möglichst effizienten,
laufzeit- oder speicheroptimierten Maschinencode zu erzeugen; Datenflussanalysen sind deshalb ein fester Bestandteil moderner Übersetzerprogramme.
Datenflussbetrachtungen sind aber auch hilfreich, um beim Testen von
Software möglichst aussagekräftige Testfälle auszuwählen und sicherzustellen,
dass alle Teile eines Softwaresystems ausreichend getestet werden.
Copyright © Fraunhofer IESE 2008
1
Datenflüsse und Datenfluss-Spezifikationen
1.3
Datenfluss-Spezifikationen
Im Idealfall ermöglicht eine korrekte und vollständige Datenflussanalyse sogar
ein formales Verifizieren der Korrektheit eines Programms — dem sind
aufgrund der Komplexität heutiger Software jedoch enge Grenzen gesetzt. In
der Praxis ist es oft nicht möglich, Datenflüsse vollautomatisch zu ermitteln. Eine
vollständige Analyse scheitert oft an zu hohem Rechenaufwand oder einfach
daran, dass der innere Aufbau einer Software-Komponente nicht bekannt ist,
sondern nur eine Beschreibung des Schnittstellenverhaltens zur Verfügung
steht.
Daher ist es oft erforderlich, Teile eines Datenflusses manuell zu spezifizieren;
die Spezifikation kann in automatische Datenfluss-Analysewerkzeuge
eingespeist werden, um »blinde Flecken« des Werkzeugs zu überbrücken und
semantisches Wissen beizusteuern, das — aus praktischer Erwägung oder
aufgrund fundamentaler Beschränkungen der Logik — nicht automatisch
berechenbar ist.
1.4
Die Datenfluss-Beschreibungssprache Flow
Der vorliegende Bericht beschreibt Flow, einer Sprache zur Spezifikation von
Datenflüssen in objektorientierten Programmen. Flow wurde im Rahmen des
Projekts SecFlow [Peine et al. 2006, Mandel&Peine 2007] entwickelt, dessen
Zielsetzung es ist, sicherheitskritische Datenflüsse in Quellcode mittels
werkzeuggestützter Analyse zu ermitteln.
In SecFlow werden Flow-Spezifikationen zum einen genutzt, um
sicherheitsrelevante Datenquellen und Datensenken zu bezeichnen. Zum
anderen dienen sie dazu, das Datenflussverhalten von Software-Komponenten
zu charakterisieren, deren Inneres jenseits des Analysehorizonts liegt oder deren
Quellcode für Analysen nicht zur Verfügung steht: Eine geeignete FlowCharakterisierung ermöglicht es, Datenflüsse auch durch opaque SoftwareKomponenten hindurch weiterzuverfolgen. Unsere SecFlow-Analyseumgebung
verfügt über Werkzeuge zur Erstellung, Prüfung und Übersetzung von FlowSpezifikationen [Wagner 2008].
Die folgende Beschreibung gliedert sich wie folgt: Kapitel 2 skizziert den
Spezifikationsansatz der Sprache Flow. Kapitel 3 beschreibt die grundlegende
Struktur von Flow-Spezifikationen und die dafür verfügbaren Sprachelemente.
Kapitel 4 stellt die verschiedenen Anweisungstypen vor, die Flow zur
Charakterisierung eines Datenflussverhaltens bietet. Kurze Flow-Beispiele
illustrieren in den Kapiteln 3 und 4 die Verwendung der Flow-Sprachmittel.
2
Copyright © Fraunhofer IESE 2008
Spezifizieren in Flow
2
Spezifizieren in Flow
Die Sprache ››Flow‹‹ wurde konzipiert, um bestimmte Teile eines
objektorientierten Programms in ihrem Datenflussverhalten zu beschreiben. Dies
können Teile sein, welche implizit benötigt werden (Bibliotheken, vordefinierte
Datentypen), aber nicht im Quelltext zu Verfügung stehen. Es können aber auch
Programmteile außerhalb des Analysehorizonts sein, deren Quelltext zwar
verfügbar ist, die aber nur auf ihr Datenflussverhalten reduziert werden sollen:
Die Analyse der Flow-Beschreibung einer Bibliothek ist deutlich schneller als die
Analyse der entsprechenden Bibliothek. Eine Datenfluss-Spezifikation ist
außerdem für vordefinierte Sprachkonstrukte/Typen der Analysesprache
unumgänglich, um die Semantik nachzubilden, die ihnen durch den Compiler
(bzw. den Interpreter) zugeschrieben wird.
Den Aspekt des Datenflusses einer Klasse beschreiben wir durch eine
sogenannte Flow Summary — eine Zusammenfassung des Datenflussverhaltens
auf abstraktem Niveau. Die Flow Summary beschränkt sich ausschließlich auf die
Datenfluss-Eigenschaften der Klasse; sie spezifiziert nicht deren gesamtes
Verhalten.
Im Folgenden beschreiben wir die Struktur einer Flow Summary, die
verschiedenen Flow-Sprachelemente und die Anwendung von Flow zur
Modellierung von Datenflüssen. Anhand einiger beispielhafter Klassen
illustrieren wir die Verwendung der Flow-Sprachelemente. Zur Beschreibung der
Sprache Flow verwenden wir folgende Konventionen:
„ Metasymbole der Flow-Grammatik werden im Schrifttyp »METASYMBOL«
gesetzt.
„ Platzhalter werden immer mit <> eingeschlossen und im Schrifttyp
»<Platzhalter>« gesetzt. Sie sind in der Spezifikation durch sinngemäße
Werte zu ersetzen.
„ Terminalsymbole, zum Beispiel Flow-Schlüsselwörter oder Operatorzeichen,
werden im Schrifttyp »Flowtext« gesetzt. Sie sind buchstabengetreu in
die Flow-Spezifikation zu übernehmen.
„ Einzelne Flow-Anweisungen und die verschiedenen Abschnitte einer Flow
Summary werden durch eine oder mehr neue Zeilen voneinander getrennt.
Copyright © Fraunhofer IESE 2008
3
Grundstruktur einer Flow Summary
3
Grundstruktur einer Flow Summary
Die Grundstruktur einer Flow Summary sieht wie folgt aus:
FLOWFILE ::=
3.1
LANGUAGEHINT
IMPORTS
SUMMARYDEFINITIONS
Language Hint
Der Language Hint zeigt an, für welche Analysesprache eine Flow Summary
geschrieben wurde. Obwohl die Flow Summary selber nicht sprachabhängig ist,
werden je nach Analysesprache unterschiedliche Name Resolver für die
Normalisierung der Typnamen verwendet. Daher ist dieser Hinweis
verpflichtend. Er sieht folgendermaßen aus:
LANGUAGEHINT ::=
3.2
@'Java' | @'.NET'
Imports
Mit Hilfe der Imports werden zwei Ziele erreicht: Erstens werden alle in der
entsprechenden Flow Summary verwendeten Klassen importiert. Dies ist für alle
in der Summary verwendeten Klassen nötig! Außerdem können Klassennamen
mit Hilfe von Umschreibungsregeln Kurzbezeichner zugeordnet werden, welche
im weiteren Verlauf der Summary an Stelle des vollen Klassennamens
verwendet werden.
Die Import-Klausel hat folgendes Format:
IMPORTS
IMPORT
::=
::=
IMPORT [IMPORTS]
<local classname> [ ='<original classname>'] @ [<package> ]
Dies bewirkt einen Import der Klasse <original classname> aus dem Package
<package> und definiert für den weiteren Verlauf der Flow Summary den lokalen
Bezeichner <local classname> für diese Klasse.
<original classname> und <package> sind die exakten Bezeichnungen des
Klassennamens bzw. des Package-Namens in der Analysesprache.
<local classname> darf nur Buchstaben und Ziffern enthalten.
Wenn die Umschreibung (mittels ='<original classname>' ) weggelassen wird,
muss <local classname> dem tatsächlichen Klassennamen in <package> entsprechen.
Importe und Umbenennungen sind nur für Klassen vorgeschrieben und
möglich.
Copyright © Fraunhofer IESE 2008
5
Grundstruktur einer Flow Summary
Beispiel 1
Verwendung von Imports
@'Java'
// benennt java.lang.String lokal in str um
str='String' @ [java.lang]
// benennt java.lang.Object lokal in obj um
obj=’Object’ @ [java.lang]
3.3
Summary-Definitionen
Die Syntax der eigentlichen Summaries hat folgende Form:
SUMMARYDEFINITIONS ::=
SUMMARY
::=
SUMMARY [SUMMARYDEFINITIONS]
summary <name> [ : SUPERTYPES] {
[ATTRIBUTES]
[INITIALIZERS]
[METHODS]
}
Eine Summary beschreibt eine Klasse bzw. Übersetzungseinheit. Der Name der
korrespondierenden Klasse ist <name>.
SUPERTYPES ::=
SUPERTYPE ::=
SUPERTYPE [, SUPERTYPES ]
<parent class> | <flow name>
ist eine kommagetrennte Liste mit Bezeichnern. Die Summary erbt
von allen Summaries, welche durch diese Bezeichner repräsentiert werden.
Typen oder Interfaces der Analysesprache sind als Bezeichner auch erlaubt,
allerdings haben diese keinen Effekt: Die Möglichkeit, in Flow auch Typ- oder
Interface-Angaben zu berücksichtigen, wurde für zukünftige Erweiterungen
offen gehalten.
SUPERTYPES
Beispiel 2
Summary-Definition
…
summary MyString : String {
…
}
3.4
Attribute
Innerhalb einer Summary können beliebig viele Attribute definiert werden.
ATTRIBUTES ::=
ATTRIBUTE [ATTRIBUTES]
Attribute sind lediglich Behelfskonstrukte, um Datenflüsse durch die von der
Summary beschriebenen Datentypen hindurch darstellen zu können. Attribute
haben selbst keinen Typ. Sie werden wie Objektvariablen mit unbeschränktem
6
Copyright © Fraunhofer IESE 2008
Grundstruktur einer Flow Summary
Zugriff behandelt. Die Definition eines Attributs im Rumpf einer Klasse definiert
gleichzeitig aber auch ein Klassenattribut. Unterschieden wird zwischen diesen
beiden nur durch die Art des Zugriffs (Typ.Attribut für Klassenattribute,
Ausdruck.Attribut für Attribute). Eine Attribut-Deklaration hat daher die
folgende Form:
ATTRIBUTE ::=
<attribute>; | <attribute> {
[STATEMENTS]
}
Attributnamen dürfen nur aus Buchstaben und Ziffern bestehen, wobei der
Name mit einem Buchstaben beginnen muss.
Eine spezielle Form des Attributes kann Flow-Statements enthalten. Diese FlowStatements werden bei jedem Zugriff auf das Attribut ausgewertet. Sie
entsprechen also einer Funktion ohne Argumente.
Dieser Mechanismus wird zum Beispiel für das Attribut tostring verwendet,
das man zur Modellierung des folgenden Sachverhalts benötigt :Die toStringMethode einer java.lang.List ruft rekursiv die toString-Methoden aller in
der Liste enthaltenen Objekte auf und baut deren Rückgabewerte in den
eigenen Rückgabewert ein. Es ist aber nicht möglich, in einer durch Flow
beschriebenen Methode Java-Methoden aufzurufen, so wie es in diesem Falle
nötig wäre. Um den Sachverhalt trotzdem darstellen zu können, wurde das
Attribut tostring mit zusätzlichen Flow-Statements eingeführt. Die toStringMethoden aller Collections greifen auf das tostring-Attribut der in ihnen
enthaltenen Objekte zu und führen dadurch zur Auswertung entsprechender,
im Attribut definierter, Flow-Statements. Diese Konstruktion ist auch für andere,
ähnliche Beziehungen zwischen in Flow spezifizierten Klassen denkbar.
Beispiel 3
Verwendung von Attributen
…
summary MyString : String {
length;
tostring {
<- this.length
}
}
3.5
(Statische) Initialisierer
In den Initialisierern werden — ähnlich einem Konstruktur in konventionellen
Programmiersprachen — Vorbereitungen für das Objekt getroffen. Ein
Initialisierer in Flow unterscheidet sich allerdings unter anderem von
Konstruktoren anderer Sprachen dadurch, dass er keine Parameter akzeptiert. Er
kann zusätzlich zu den Konstruktoren der Analysesprache existieren, die auch in
Copyright © Fraunhofer IESE 2008
7
Grundstruktur einer Flow Summary
der Summary modelliert werden können. Allerdings werden beim Instanziieren
einer Klasse, die durch eine Flow Summary beschrieben wird, zuerst die FlowInitialisierer interpretiert. Die Interpretation des Konstruktors der Analysesprache
findet erst danach statt, sobald das Objekt durch Flow fertig vorbereitet wurde.
Die Syntax der Initialisierer ist die folgende:
INITIALIZERS
INITIALIZER
::=
::=
INITIALIZER [INITIALIZERS]
[static] {
[STATEMENTS]
}
Statische Initialisierer (die noch vor der Initialisierung von Objekten — und dann
genau einmal — interpretiert werden) verwenden das Flow-Schlüsselwort
static. Sie werden ausgewertet, sobald die entsprechende Klasse im
Analyseumfang referenziert wurde.
Beispiel 4
Flow-Initialisierer
…
summary MyString : String {
length;
{
this.length <- [Integer]
}
}
Die Reihenfolge der Umwandlung der Initialisierer und Konstruktoren bei der
ersten Referenzierung einer Klasse im Analyseumfang wie in Beispielzeile
Collection c = (Collection) new Hashtable<Integer,String>();
ist folgende:
1. Wenn bei der Analyse des Call Graphs festgestellt wird, dass diese Zeile im
Programm erreichbar ist, wird der statische Initialisierer der Klasse Hashtable
vor dem Beginn der Analyse des Programms angewendet, sofern er existiert.
2. Sobald ein neues Objekt der entsprechenden Klasse (in diesem Fall
Hashtable) instanziiert wird, wird der (nicht-statische) Initialisierer der Klasse
angewendet, sofern er existiert.
3. Als letztes wird der Konstruktor der Klasse, welcher bei der Instanziierung
verwendet wird, angewendet.
Initialisierer werden auch angewendet, wenn Anweisungen innerhalb einer
Flow Summary neue Flow-Objekte instanziieren bzw. Flow-Klassen
referenzieren. Allerdings wird in diesem Fall kein Konstruktor der
Analysesprache angewendet.
8
Copyright © Fraunhofer IESE 2008
Grundstruktur einer Flow Summary
3.6
Konstruktoren und Methoden
Um die Datenflüsse einer Klasse vollständig und möglichst genau modellieren
zu können, muss auch spezifiziert werden, welche Datenflüsse beim Aufruf von
Methoden bzw. Konstruktoren stattfinden. Dazu wird die Methode bzw. der
Konstruktor der Analysesprache in Flow mit einer mit der Java-Syntax
identischen Syntax und Semantik notiert, wobei Konstruktoren durch Methoden
ohne Rückgabewert dargestellt werden.
METHODS
METHOD
PARAMETERLIST
Beispiel 5
::= METHOD [METHODS]
::= [<return type> ] <method name> ( [PARAMETERLIST] ) {
[STATEMENTS]
}
::= <parameter type> <parameter name> [, PARAMETERLIST]
Datenfluss-Spezifikation für Methoden und Konstruktoren
…
summary MyString : String {
…
String(char[] chars) {
…
}
String append(String tail){
…
}
}
Copyright © Fraunhofer IESE 2008
9
Flow-Anweisungen
4
Flow-Anweisungen
Flow-Anweisungen (in den obigen Beispielen als Statements bezeichnet)
drücken die Erzeugung von Daten, die eigentlichen Datenflüsse und das
Abfließen von Daten in Senken aus. Sie können nur in Initialisierern und
Methoden eingesetzt werden.
STATEMENTS ::=
STATEMENT ::=
STATEMENT [STATEMENTS]
BINDING | FLOW | RETURNFLOW | PIPE |
OBJECTGENERATOR | DATAGENERATOR | DATACONSUMPTION
Die verschiedenen Arten von Statements werden in den folgenden Kapiteln
noch weiter erläutert.
4.1
Eingangs- und Ausgangsbezeichner einer Flow-Anweisung
Als Ziel- und Startpunkt von Datenflüssen werden Bezeichner benötigt. Ein
Bezeichner besteht aus einem oder mehreren verschiedenen Elementen, welche
durch Punkte voneinander getrennt sind. Ein solch zusammengesetzter
Bezeichner wird hier auch als Pfad bezeichnet.
Elemente eines Pfades können Klassenattribute, Attribute oder lokale Variablen
sein (Parameter werden wie lokale Variablen betrachtet). Die Syntax eines Pfads
ist:
PATH
PATHREST
::=
::=
{<local variable> | <type> | this } [PATHREST]
{<attribute> | []} [PATHREST]
Auf Klassenattribute wird über die Angabe des Klassennamens im
Bezeichnerpfad zugegriffen, auf Objektvariablen über den Namen des Objekts
bzw. das this Schlüsselwort im Bezeichnerpfad. Ein [] steht für den Zugriff
auf ein Array.
Beispiel 6
Flow-Bezeichner
…
this.file
shop.items[].length
String.stringcount
…
// Instanzattribut
// Instanzattribut
// Klassenattribut
Zusätzlich zu Klassen-Attributen können in Flow auch lokale Variablen definiert
werden und auch in Pfaden eingesetzt werden. Sie werden implizit bei der
ersten Verwendung definiert und überschrieben, wie auch z.B. in Java,
Copyright © Fraunhofer IESE 2008
11
Flow-Anweisungen
gleichnamige Attribute der Klasse nur in ihrem lokalen Kontext (mittels
this.attribute kann auch in diesem lokalen Kontext explizit auf das
Instanzattribut zugegriffen werden).
4.2
Binden von Quellen und Senken
Daten, die von außerhalb des Systems — also vom Benutzer oder anderen
Systemen — in das Programm einfließen, betreten das Programm durch eine
Quelle. Eine Quelle kann eine Datei, ein Netzwerkport, ein HTTP-Request oder
ähnliches sein. Senken stellen Kontaktstellen des Programms mit seiner
Außenwelt dar, durch welche Daten nach außen abfließen. Quellen und Senken
müssen mit Hilfe von sogenannten Bindings mit Bezeichnern in Flow verbunden
werden. Die so gebundenen Quellen und Senken werden in der FlowSpezifikation durch den Bezeichner repräsentiert. Der Binding-Operator hat
folgende Syntax:
BINDING ::=
PATH ~ [source('<type>') | sink('<type>') | PATH ]
<type> steht für die Art der Quelle bzw. Senke und wird von Flow und dem
Analyse-Werkzeug nicht vorgegeben und nicht interpretiert. Bei der Ausgabe
der Analyseergebnisse wird der <type> als Ursprung bzw. Ziel des Datenflusses
angegeben, eine aussagekräftige Bezeichnung ist also sinnvoll.
Beispiel 7
Binden von Quellen und Senken
…
summary File {
filesystem;
{
this.filesystem ~ sink('FILESYSTEM')
this.filesystem ~ source('FILESYSTEM')
}
}
Quellen und Senken können zwischen verschiedenen Objekten propagiert
werden (durch den '~'-Operator): Dafür steht zu beiden Seiten des ~ Operators
ein Bezeichnerpfad, von denen der Bezeichnerpfad auf der rechten Seite schon
an eine Quelle gebunden sein muss. Durch eine solche Konstruktion wird der
Bezeichnerpfad auf der linken Seite auch an die gebundenen Quellen des
Bezeichnerpfads auf der rechten Seite gebunden. Das folgende Beispiel bringt
eine Begründung für ein solches Vorgehen:
Beispiel 8
Propagieren gebundener Quellen und Senken
…
summary FileWriter : Writer {
name;
12
Copyright © Fraunhofer IESE 2008
Flow-Anweisungen
FileWriter(File file) {
this.snk ~ file.filesystem
this.name <- file.name
}
FileWriter(String name) {
this.snk ~ sink('FILESYSTEM')
this.name <- name
this.snk <~ String(name)
}
}
Für den Fall, dass der FileWriter(String name) Konstruktor benutzt wird,
wird an das Attribut this.snk eine Senke zum Dateisystem gebunden. Hier ist
die Aufgabe des Konstruktors, ein File-Objekt mit dem Namen name zu
erzeugen und direkt dahin zu schreiben. Es handelt sich hier um ein
Internalisieren von Effekten, die erst im File-Objekt auftauchen würden.
Für den anderen Fall, dass der FileWriter(File file) Konstruktor benutzt
wird, wird die Senke this.snk des FileWriters mit den Senken des FileObjekts am Attribut file.filesystem verbunden. Man muss hier beachten,
dass es sich bei file auch um ein Objekt einer Unterklasse von File handeln
kann, welche nicht direkt an die Senke des Dateisystems angebunden ist. Um
diese Flexibilität der Objektorientierung zu wahren wird hier der
Bindungsoperator verwendet anstatt this.snk direkt an eine Senke zu binden,
wie im anderen Konstruktor.
4.3
Flows
Flows stehen für einen einfachen, nicht-verändernden Datenfluss vom
Bezeichner auf der rechten Seite (dem Ursprung des Flows), zum Bezeichner auf
der linken Seite (dem Ziel des Flows). Flow-Klauseln haben folgende Syntax:
FLOW
RETURNFLOW
OUTPUT
INPUT
::=
::=
::=
::=
OUTPUT <- INPUT
<- INPUT
PATH
FUNCTION | OBJECTGENERATOR | PATH
Der Ursprung (INPUT) des Flows kann eine Funktion, eine Objekterzeugung oder
ein Bezeichnerpfad sein, während das Ziel (OUTPUT) nur ein Bezeichnerpfad sein
darf. Beim RETURNFLOW handelt es sich um den Rückfluss an die aufrufende
Methode, daher wird kein Ziel für den Flow-Operator benötigt.
Der Flow-Operator unterscheidet sich vom Pipe-Operator, bei dem beide
Bezeichner nicht-lokal sind. Siehe dazu Abschnitt 4.5.
Copyright © Fraunhofer IESE 2008
13
Flow-Anweisungen
Beispiel 9
Spezifizieren von Flüssen
…
summary String {
…
String append(String tail){
…
tmp <- tail
<- tmp
}
}
4.4
Funktionale Ausdrücke
Datenflüsse können unverändert fließen oder auf ihrem Weg signifikante
Umformungen erfahren. In Flow werden solche Datenflusstransformationen
durch Flow-Funktionen ausgedrückt. Das Auftreten einer Funktion signalisiert,
dass die Daten auf ihrem Weg eine bedeutsame Änderung erfahren. Was in
diesem Zusammenhang als »bedeutsam« anzusehen ist, hängt von den
Analysezielen des Modellierers ab. Da Flow für Sicherheitsanalysen konzipiert
wurde, werden derzeit vor allem sicherheitsrelevante Umformungen durch
entsprechende Flow-Funktionen repräsentiert.
Funktionen haben einen Namen (der die Art der Transformation der Argumente
definiert) und eine Argumentliste.
FUNCTION
INPUTS
ANNOTATIONS
ANNOTATION
VALUESET
::=
::=
::=
::=
::=
<function name> [ < ANNOTATIONS > ] ( [INPUTS] )
INPUT [ , INPUTS]
ANNOTATION [ , ANNOTATIONS]
<attribute name> = ['<value>' | { VALUESET } ]
'<value>' [ , VALUESET ]
Derzeit sind folgende Funktionen definiert:
Tabelle 1
14
Vordefinierte Flow-Funktionen
Funktion
Bedeutung
sanitize<safe='<sink>'>(<origin>)
Die Daten aus <origin> werden bzgl.
Senken vom Typ <sink> bereinigt, d.h.
sie werden so umgeformt, dass sie für
den angegebenen Senken-Typ keine
Gefahr mehr darstellen. (Ein typischer
Bereinigungsoperator ist zum Beispiel
das sogenannte Escaping von
Steuerzeichen in Nutzdaten.)
Copyright © Fraunhofer IESE 2008
Flow-Anweisungen
Funktion
Bedeutung
enable<unsafe='<sink>'>(<origin>)
Die (zuvor vielleicht sicheren) Daten aus
<origin> werden vor Weitergabe so
umgeformt, dass dadurch deren
Ungefährlichkeit in Bezug auf Senken
des Typs <sink> nicht mehr
gewährleistet ist. (Es werden zum
Beispiel Escape-Zeichen entfernt,
sodass ursprünglich inaktive
Steuerzeichen wieder reaktiviert
werden.)
Die enable-Funktion hebt ein
vorangegangenes sanitize wieder
auf.
split(<origin>)
Die Argumentdaten in werden
gestückelt weitergegeben.
slice(<origin>)
Die Argumentdatenwerden in Teilen
weitergegeben.
chain(<origin1>,<origin2>,…)
Es wird eine Kombination von <origin1>,
<origin2> ... weitergegeben. (Ein
typisches Anwendungsbeispiel ist die
Modellierung von StringKonkatenation.)
translate(<origin>)
Die Daten werden in eine andere
Darstellung übersetzt (z.B. Latin1 →
UTF8)
todata(<origin>)
Die Daten werden in einen anderen
Datentyp umgewandelt (z.B. '9' → 9)
totext(<origin>)
Die Daten werden in eine textuelle
Darstellung gebracht (9 → '9')
Beispiele für die Verwendung dieser Funktionen finden sich im Anhang, siehe
Beispiel 14 bis Beispiel 16 ab Seite 21.
Dem Flow-Anwender steht es frei, eigene Funktionsnamen mit frei gewählten
Annotationen zu verwenden. Der Flow-Compiler interpretiert benutzerdefinierte
Funktionen nicht, sondern fügt lediglich die gewählten Bezeichner an den
entsprechenden Stellen in den Datenfluss ein. Die Interpretation der Funktionsnamen und Annotationen obliegt der nachgeordneten Datenflussanalyse. Die in
Tabelle 1 genannten, vordefinierten Funktionen sind auf das SecFlow-
Copyright © Fraunhofer IESE 2008
15
Flow-Anweisungen
Analyseframework zugeschnitten und haben dort eine genau festgelegte
Semantik.
4.5
Pipes
Eine besondere Form des Flow-Operators ist der Pipe-Operator. Syntaktisch
entspricht er einem Flow-Operator zwischen zwei nicht lokalen Variablen.
Wenn eine Pipe im Analyselauf vorkommt, entsteht eine permanente
Verbindung zwischen dem Ursprungs- und dem Zielsausdruck.
Im Beispiel bedeutet das, dass beim Aufruf der Methode write zunächst der
Inhalt des Parameters str in this.buffer fließt. Wenn die Pipe zwischen
file.content und this.buffer bereits erzeugt wurde — durch einen
vorhergehenden Aufruf von setFile — so bewirkt ein Fluss in this.buffer
auch gleichzeitig einen Fluss nach file.content. Der Effekt einer Pipe ist also
nicht lokal beschränkt.
Beispiel 10
Verwendung von Pipes
…
summary FileProxy {
…
void setFile(File file){
file.content <- this.buffer
}
void write(String str){
this.buffer <- str
}
}
4.6
Objekt-Instanziierung
Bei der Objekt-Instanziierung wird an dem durch den Zielbezeichner (PATH)
angegebenen Konstrukt ein neues Objekt vom Typ Type erzeugt.
OBJECTGENERATOR
TYPE
ARRAYS
::=
::=
::=
PATH <- [ TYPE ]
<local classname> [ARRAYS]
[][ARRAYS]
Eine Objekt-Instanziierung in Flow führt dazu, dass nur der Initialisierer des
erzeugten Objekts interpretiert wird. Evtl. wurde vorher noch der statische
Initialisierer interpretiert. Ein Konstruktor wird in diesem Fall nicht interpretiert.
Beispiel 11
Objektinstanziierung
…
summary String {
16
Copyright © Fraunhofer IESE 2008
Flow-Anweisungen
…
String append(String tail){
tmp <- [String]
tmp <- chain(this, tail)
<- tmp
}
}
4.7
Daten-Generierung/Konsumierung
Um auszudrücken, dass Daten einer Quelle gelesen werden, wird der an die
Quelle gebundene Bezeichner mit dem Operator <* und einer Typangabe als
Ursprung des Datums dargestellt:
DATAGENERATOR
::=
PATH <* TYPE ( INPUT )
Sollte INPUT nicht an eine Quelle gebunden sein, kommt es dadurch nicht zu
einem Fehler. Die Analyse interpretiert das dann als Lesen von einer sicheren
Quelle.
Um auszudrücken, dass Daten in eine Senke abfließen, lautet die Syntax:
DATACONSUMPTION
::=
PATH <~ TYPE ( INPUT)
Das Schreiben in nicht an Senken gebundene Bezeichner verhält sich analog
zum Lesen. Das heißt, alle nicht gebundenen Senken-Bezeichner werden in der
Analyse als unkritische Senken interpretiert.
Beispiel 12
Spezifizieren eines Datengenerators
…
Summary FileReader {
in
…
String read() {
tmp <- [String]
tmp <* String(data)
<- tmp
}
}
Beispiel 13
Spezifizieren eines Datenkonsumenten
…
Summary FileWriter {
out
…
void write(String data) {
out <~ String(data)
}
Copyright © Fraunhofer IESE 2008
17
Flow-Anweisungen
}
18
Copyright © Fraunhofer IESE 2008
Tabellenverzeichnis
5
Fazit
Die Sprache Flow wurde entwickelt, um Datenflüsse in Programmen zu
beschreiben. Die Sprachmittel von Flow sind auf objektorientierte Zielsprachen
zugeschnitten, da sich die Datenflussdeklaration auf Klassen und Methoden
beziehen. Konkret unterstützt der derzeit verfügbare Flow-Compiler die
Programmiersprachen Java und C#. Allerdings wurden die Konzepte der
Sprache so allgemein gehalten, dass eine Erweiterung auf andere Sprachen mit
vertretbarem Aufwand möglich sein sollte.
Um keine Bezeichnungskonflikte mit den Sprachelementen der Zielsprachen
heraufzubeschwören, verwendet Flow bewusst eigene, in anderen Sprachen
nicht gebräuchliche Operatoren und Beschreibungsmittel, lehnt diese aber an
gebräuchliche Syntaxkonventionen an, um das Erlernen der Sprache zu
erleichtern.
Flow wurde mit dem Ziel konzipiert, sicherheitsrelevante Aspekte von
Datenflüssen zu modellieren. Dies spiegelt sich am deutlichsten in den
verfügbaren Flow-Funktionen (vgl. Abschnitt 4.4, Tabelle 1) wider, die in
besonderem Maße Sicherheitstransformationen (z.B. sanitize, enable)
adressieren. Es spricht jedoch nichts dagegen, den Sprachumfang um neue
Funktionsbezeichner zu erweitern, um je nach Bedarf auch andere relevante
Datenflussaspekte in der Spezifikation zu repräsentieren.
Copyright © Fraunhofer IESE 2008
19
Anhang —
Anwendungsbeispiele
Anhang — Anwendungsbeispiele
Im Folgenden sind mehrere Flow-Spezifikation dargestellt, die das
Zusammenspiel der Flow-Sprachelemente noch einmal in einem etwas
umfassenderen Kontext anhand realer Anwendungsfälle illustrieren.
Die Benutzung der verschiedenen Flow Funktionen lässt sich sehr gut an der
Flow Repräsentation der Klasse java.lang.String darstellen. Für diese Klasse
ist kein Quellcode verfügbar, so dass eine Modellierung mit Flow unerlässlich ist,
falls java.lang.String innerhalb des Analysehorizonts verwendet wird.
Beispiel 14
Ausschnitt der Flow-Spezifikation der Klasse java.lang.String
@'Java'
StringBuffer @ [java.lang]
Object @ [java.lang]
String @ [java.lang]
Locale @ [java.util]
CharSequence @ [java.lang]
summary String : CharSequence, Object {
tostring {
<- this
}
String(String value) {
this <- value
}
int codePointAt(int index){
<- translate(slice(this))
}
String[] split(String regex){
result <- [String[]]
result[] <- split(this)
<- result
}
String copyValueOf(char[] data, int offset, int count){
<- slice(data.tostring)
}
String format(Locale l, String format, Object[] args){
<- chain(format, args[].tostring)
}
byte[] getBytes(){
result <- [byte[]]
result[] <- split(todata(this))
Copyright © Fraunhofer IESE 2008
21
Anhang — Anwendungsbeispiele
result.tostring <- this
<- result
}
String toLowerCase() {
<- translate(this)
}
String valueOf(boolean b) {
<- totext(b)
}
String valueOf(char[] data) {
<- data.tostring
}
String toString() {
<- this
}
}
Die Benutzung der Flow Funktionen sanitize und enable wird an der jeweils
vollständigen Flow-Spezifikation der Klassen java.net.URLEncoder und
java.netURLDecoder ersichtlich:
Die Methode encode in java.netURLEncoder encodiert den ersten Parameter
gemäß dem Namen der Encodierung im zweiten Parameter, so dass ein String
im x-www-form-urlencoded Format entsteht und zurückgegeben wird. Diese
Transformation stellt bezüglich der Senke HTTP eine »Entschärfung« des
Datums dar — Es kann nun unverändert in eine HTTP Senke, zum Beispiel eine
HTTP-Response, eingebettet werden, ohne den Empfänger der Daten in Gefahr
zu bringen.
Beispiel 15
Flow-Spezifikation der Klasse.java.net.URLEncoder
@'Java'
URLEncoder @ [java.net]
String @ [java.lang]
summary URLEncoder {
String encode(String string, String enc) {
<- sanitize<safe='HTTP'>(translate(string))
}
}
Die Methode decode in java.netURLEncoder decodiert den ersten Parameter
aus dem x-www-form-urlencoded Format gemäß dem Namen der Encodierung
im zweiten Parameter. Diese Transformation stellt bezüglich der Senke HTTP
eine »Verschärfung« des Datenflusses dar: Die Daten, die eventuell über einen
HTTP-Request aus unsicherer Quelle in die Applikation gelangt sind, eignen sich
22
Copyright © Fraunhofer IESE 2008
Anhang —
Anwendungsbeispiele
nun nicht mehr für eine unveränderte Einbettung in die HTTP-Response (Gefahr
von sogenanntem Cross-Site Scripting).
Beispiel 16
Flow-Spezifikation der Klasse.java.net.URLDecoder
@'Java'
URLDecoder @ [java.net]
String @ [java.lang]
summary URLDecoder {
String decode(String string, String enc) {
<- enable<unsafe='HTTP'>(translate(string))
}
}
Copyright © Fraunhofer IESE 2008
23
Abkürzungsverzeichnis
Abkürzungsverzeichnis
HTTP
Copyright © Fraunhofer IESE 2008
Hypertext Transfer Protocol
25
Quellenverzeichnis
Quellenverzeichnis
[Mandel&Peine 2007]
Stefan Mandel, Holger Peine: The SecFlow Source Code Security
Checker: Current Problems and Solution Alternatives. 23. Annual
Computer Security Application Conference (ACSAC’07), Miami, Florida,
Dezember 2007
[Peine et al. 2006]
Holger Peine, Stefan Mandel, Dana Richter: SecFlow: Automatische
Ermittlung sicherheitskritischer Datenflüsse in Quellcode.
Statuskonferenz Forschungsoffensive »Software Engineering 2006«,
Juni 2006
[Wagner 2008]
Andreas Wagner: Implementierung einer Werkzeugunterstützung zur
Spezifikation und Visualisierung von Sicherheitsschwachstellen.
Praktikumsbericht, Fraunhofer IESE / Fachhochschule Kaiserslautern,
Kaiserslautern, August 2008
Copyright © Fraunhofer IESE 2008
27
Dokument-Information
Titel:
Flow — Eine DatenflussBeschreibungssprache für
objektorientierte Programme
Datum:
November 2008
Report:
IESE-Report Nr. 086.08/D
Status:
final
Klassifikation: public
Das vorliegende Dokument entstand im Rahmen des
SecFlow-Projekts. Das Verbundprojekt »SecFlow:
Automatische Erkennung sicherheitskritischer Datenflüsse
in Quellcode« wird vom Bundesministerium für Bildung
und Forschung (BMBF) unter dem Förderkennzeichen
01ISF09C gefördert.
Copyright 2008, Fraunhofer IESE.
Alle Rechte vorbehalten. Diese Veröffentlichung
darf für kommerzielle Zwecke ohne vorherige
schriftliche Erlaubnis des Herausgebers in keiner
Weise, auch nicht auszugsweise, insbesondere
elektronisch oder mechanisch, als Fotokopie
oder als Aufnahme oder sonst wie vervielfältigt,
gespeichert oder übertragen werden. Eine
schriftliche Genehmigung ist nicht erforderlich
für die Vervielfältigung oder Verteilung der
Veröffentlichung von bzw. an Personen zu
privaten Zwecken.
Zugehörige Unterlagen
Herunterladen