Skip to content

Commit 8fa6140

Browse files
committed
C#: Add data extensions version of the model generator scripts.
1 parent f181d66 commit 8fa6140

File tree

3 files changed

+235
-1
lines changed

3 files changed

+235
-1
lines changed

csharp/ql/src/utils/model-generator/GenerateFlowModel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
# Add Model as Data script directory to sys.path.
88
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
9-
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/") # Note that this has been changed.
9+
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
1010
sys.path.append(madpath)
1111

1212
import generate_flow_model as model
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/python3
2+
3+
import sys
4+
import os.path
5+
import subprocess
6+
7+
# Add Model as Data script directory to sys.path.
8+
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
9+
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
10+
sys.path.append(madpath)
11+
12+
import generate_flow_model_extensions as model
13+
14+
language = "csharp"
15+
model.Generator.make(language).run()
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#!/usr/bin/python3
2+
3+
import helpers
4+
import json
5+
import os
6+
import os.path
7+
import shlex
8+
import subprocess
9+
import sys
10+
import tempfile
11+
12+
def quote_if_needed(row):
13+
if row != "true" and row != "false":
14+
return "\"" + row + "\""
15+
# subtypes column
16+
return row
17+
18+
def parseData(data):
19+
rows = ""
20+
for (row) in data:
21+
d = row[0].split(';')
22+
d = map(quote_if_needed, d)
23+
rows += " - [" + ', '.join(d) + ']\n'
24+
25+
return rows
26+
27+
class Generator:
28+
def __init__ (self, language):
29+
self.language = language
30+
self.generateSinks = False
31+
self.generateSources = False
32+
self.generateSummaries = False
33+
self.generateNegativeSummaries = False
34+
self.generateTypeBasedSummaries = False
35+
self.dryRun = False
36+
self.dirname = "model-generator"
37+
38+
39+
def printHelp(self):
40+
print(f"""Usage:
41+
python3 GenerateFlowModelExtensions.py <library-database> <outputYml> [<friendlyFrameworkName>] [--with-sinks] [--with-sources] [--with-summaries] [--with-typebased-summaries] [--dry-run]
42+
43+
This generates summary, source and sink models for the code in the database.
44+
The files will be placed in `{self.language}/ql/lib/ext/generated/<outputYml>.model.yml` where
45+
outputYml is the name (and path) of the output YAML file. Usually, models are grouped by their
46+
respective frameworks.
47+
48+
Which models are generated is controlled by the flags:
49+
--with-sinks
50+
--with-sources
51+
--with-summaries
52+
--with-negative-summaries
53+
--with-typebased-summaries (Experimental - only for C#)
54+
If none of these flags are specified, all models are generated except for the type based models.
55+
56+
--dry-run: Only run the queries, but don't write to file.
57+
58+
Example invocations:
59+
$ python3 GenerateFlowModelExtensions.py /tmp/dbs/my_library_db mylibrary
60+
$ python3 GenerateFlowModelExtensions.py /tmp/dbs/my_library_db mylibrary "Friendly Name of Framework"
61+
$ python3 GenerateFlowModelExtensions.py /tmp/dbs/my_library_db --with-sinks
62+
63+
Requirements: `codeql` should both appear on your path.
64+
""")
65+
66+
67+
def setenvironment(self, target, database, friendlyName):
68+
self.codeQlRoot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
69+
if not target.endswith(".model.yml"):
70+
target += ".model.yml"
71+
filename = os.path.basename(target)
72+
if friendlyName is not None:
73+
self.friendlyname = friendlyName
74+
else:
75+
self.friendlyname = filename[:-10]
76+
self.shortname = filename[:-10]
77+
self.database = database
78+
self.generatedFrameworks = os.path.join(
79+
self.codeQlRoot, f"{self.language}/ql/lib/ext/generated/")
80+
self.frameworkTarget = os.path.join(self.generatedFrameworks, filename)
81+
self.typeBasedFrameworkTarget = os.path.join(self.generatedFrameworks, "TypeBased" + filename)
82+
self.workDir = tempfile.mkdtemp()
83+
os.makedirs(self.generatedFrameworks, exist_ok=True)
84+
85+
86+
@staticmethod
87+
def make(language):
88+
generator = Generator(language)
89+
if any(s == "--help" for s in sys.argv):
90+
generator.printHelp()
91+
sys.exit(0)
92+
93+
if "--with-sinks" in sys.argv:
94+
sys.argv.remove("--with-sinks")
95+
generator.generateSinks = True
96+
97+
if "--with-sources" in sys.argv:
98+
sys.argv.remove("--with-sources")
99+
generator.generateSources = True
100+
101+
if "--with-summaries" in sys.argv:
102+
sys.argv.remove("--with-summaries")
103+
generator.generateSummaries = True
104+
105+
if "--with-negative-summaries" in sys.argv:
106+
sys.argv.remove("--with-negative-summaries")
107+
generator.generateNegativeSummaries = True
108+
109+
if "--with-typebased-summaries" in sys.argv:
110+
sys.argv.remove("--with-typebased-summaries")
111+
generator.generateTypeBasedSummaries = True
112+
113+
if "--dry-run" in sys.argv:
114+
sys.argv.remove("--dry-run")
115+
generator.dryRun = True
116+
117+
if not generator.generateSinks and not generator.generateSources and not generator.generateSummaries and not generator.generateNegativeSummaries and not generator.generateTypeBasedSummaries:
118+
generator.generateSinks = generator.generateSources = generator.generateSummaries = generator.generateNegativeSummaries = True
119+
120+
if len(sys.argv) < 3 or len(sys.argv) > 4:
121+
generator.printHelp()
122+
sys.exit(1)
123+
124+
friendlyName = None
125+
if len(sys.argv) == 4:
126+
friendlyName = sys.argv[3]
127+
128+
generator.setenvironment(sys.argv[2], sys.argv[1], friendlyName)
129+
return generator
130+
131+
132+
def runQuery(self, query):
133+
print("########## Querying " + query + "...")
134+
queryFile = os.path.join(self.codeQlRoot, f"{self.language}/ql/src/utils/{self.dirname}", query)
135+
resultBqrs = os.path.join(self.workDir, "out.bqrs")
136+
137+
helpers.run_cmd(['codeql', 'query', 'run', queryFile, '--database',
138+
self.database, '--output', resultBqrs, '--threads', '8'], "Failed to generate " + query)
139+
140+
return helpers.readData(self.workDir, resultBqrs)
141+
142+
143+
def asAddsTo(self, rows, predicate):
144+
if rows.strip() == "":
145+
return ""
146+
return helpers.addsToTemplate.format(f"codeql/{self.language}-all", predicate, rows)
147+
148+
149+
def getAddsTo(self, query, predicate):
150+
data = self.runQuery(query)
151+
rows = parseData(data)
152+
return self.asAddsTo(rows, predicate)
153+
154+
155+
def makeContent(self):
156+
if self.generateSummaries:
157+
summaryAddsTo = self.getAddsTo("CaptureSummaryModels.ql", helpers.summaryModelPredicate)
158+
else:
159+
summaryAddsTo = ""
160+
161+
if self.generateSinks:
162+
sinkAddsTo = self.getAddsTo("CaptureSinkModels.ql", helpers.sinkModelPredicate)
163+
else:
164+
sinkAddsTo = ""
165+
166+
if self.generateSources:
167+
sourceAddsTo = self.getAddsTo("CaptureSourceModels.ql", helpers.sourceModelPredicate)
168+
else:
169+
sourceAddsTo = ""
170+
171+
if self.generateNegativeSummaries:
172+
negativeSummaryAddsTo = self.getAddsTo("CaptureNegativeSummaryModels.ql", "extNegativeSummaryModel")
173+
else:
174+
negativeSummaryAddsTo = ""
175+
176+
return f"""
177+
# THIS FILE IS AN AUTO-GENERATED MODELS AS DATA FILE. DO NOT EDIT.
178+
# Definitions of taint steps in the {self.friendlyname} framework.
179+
180+
extensions:
181+
{sinkAddsTo}
182+
{sourceAddsTo}
183+
{summaryAddsTo}
184+
{negativeSummaryAddsTo}
185+
"""
186+
187+
def makeTypeBasedContent(self):
188+
if self.generateTypeBasedSummaries:
189+
typeBasedSummaryAddsTo = self.getAddsTo("CaptureTypeBasedSummaryModels.ql", "extSummaryModel")
190+
else:
191+
typeBasedSummaryAddsTo = ""
192+
193+
return f"""
194+
# THIS FILE IS AN AUTO-GENERATED MODELS AS DATA FILE. DO NOT EDIT.
195+
# Definitions of type based summaries in the {self.friendlyname} framework.
196+
197+
extensions:
198+
{typeBasedSummaryAddsTo}
199+
"""
200+
201+
def save(self, content, target):
202+
with open(target, "w") as targetYml:
203+
targetYml.write(content)
204+
print("Models as data extensions written to " + target)
205+
206+
207+
def run(self):
208+
content = self.makeContent()
209+
typeBasedContent = self.makeTypeBasedContent()
210+
211+
if self.dryRun:
212+
print("Models as data extensions generated, but not written to file.")
213+
sys.exit(0)
214+
215+
if self.generateSinks or self.generateSinks or self.generateSummaries:
216+
self.save(content, self.frameworkTarget)
217+
218+
if self.generateTypeBasedSummaries:
219+
self.save(typeBasedContent, self.typeBasedFrameworkTarget)

0 commit comments

Comments
 (0)