Skip to content

Commit 5464d0b

Browse files
makubackimergify[bot]
authored andcommitted
BaseTools/Plugin/CodeQL: Add CodeQL build plugin
Adds a CodeQL plugin that supports CodeQL in the build system. 1. CodeQlBuildPlugin - Generates a CodeQL database for a given build. 2. CodeQlAnalyzePlugin - Analyzes a CodeQL database and interprets results. 3. External dependencies - Assist with downloading the CodeQL CLI and making it available to the CodeQL plugins. 4. CodeQlQueries.qls - A C/C++ CodeQL query set run against the code. 5. Readme.md - A comprehensive readme file to help: - Platform integrators understand how to configure the plugin - Developers understand how to modify the plugin - Users understand how to use the plugin Read Readme.md for additional details. Cc: Bob Feng <bob.c.feng@intel.com> Cc: Liming Gao <gaoliming@byosoft.com.cn> Cc: Michael D Kinney <michael.d.kinney@intel.com> Cc: Rebecca Cran <rebecca@bsdio.com> Cc: Sean Brogan <sean.brogan@microsoft.com> Cc: Yuwei Chen <yuwei.chen@intel.com> Signed-off-by: Michael Kubacki <michael.kubacki@microsoft.com> Reviewed-by: Yuwei Chen <yuwei.chen@intel.com> Reviewed-by: Sean Brogan <sean.brogan@microsoft.com> Acked-by: Laszlo Ersek <lersek@redhat.com> Acked-by: Michael D Kinney <michael.d.kinney@intel.com>
1 parent c1393bd commit 5464d0b

14 files changed

+1339
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# @file CodeQAnalyzePlugin.py
2+
#
3+
# A build plugin that analyzes a CodeQL database.
4+
#
5+
# Copyright (c) Microsoft Corporation. All rights reserved.
6+
# SPDX-License-Identifier: BSD-2-Clause-Patent
7+
##
8+
9+
import json
10+
import logging
11+
import os
12+
import yaml
13+
14+
from analyze import analyze_filter
15+
from common import codeql_plugin
16+
17+
from edk2toolext import edk2_logging
18+
from edk2toolext.environment.plugintypes.uefi_build_plugin import \
19+
IUefiBuildPlugin
20+
from edk2toolext.environment.uefi_build import UefiBuilder
21+
from edk2toollib.uefi.edk2.path_utilities import Edk2Path
22+
from edk2toollib.utility_functions import RunCmd
23+
from pathlib import Path
24+
25+
26+
class CodeQlAnalyzePlugin(IUefiBuildPlugin):
27+
28+
def do_post_build(self, builder: UefiBuilder) -> int:
29+
"""CodeQL analysis post-build functionality.
30+
31+
Args:
32+
builder (UefiBuilder): A UEFI builder object for this build.
33+
34+
Returns:
35+
int: The number of CodeQL errors found. Zero indicates that
36+
AuditOnly mode is enabled or no failures were found.
37+
"""
38+
self.builder = builder
39+
self.package = builder.edk2path.GetContainingPackage(
40+
builder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath(
41+
builder.env.GetValue("ACTIVE_PLATFORM")
42+
)
43+
)
44+
45+
self.package_path = Path(
46+
builder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath(
47+
self.package
48+
)
49+
)
50+
self.target = builder.env.GetValue("TARGET")
51+
52+
self.codeql_db_path = codeql_plugin.get_codeql_db_path(
53+
builder.ws, self.package, self.target,
54+
new_path=False)
55+
56+
self.codeql_path = codeql_plugin.get_codeql_cli_path()
57+
if not self.codeql_path:
58+
logging.critical("CodeQL build enabled but CodeQL CLI application "
59+
"not found.")
60+
return -1
61+
62+
codeql_sarif_dir_path = self.codeql_db_path[
63+
:self.codeql_db_path.rindex('-')]
64+
codeql_sarif_dir_path = codeql_sarif_dir_path.replace(
65+
"-db-", "-analysis-")
66+
self.codeql_sarif_path = os.path.join(
67+
codeql_sarif_dir_path,
68+
(os.path.basename(
69+
self.codeql_db_path) +
70+
".sarif"))
71+
72+
edk2_logging.log_progress(f"Analyzing {self.package} ({self.target}) "
73+
f"CodeQL database at:\n"
74+
f" {self.codeql_db_path}")
75+
edk2_logging.log_progress(f"Results will be written to:\n"
76+
f" {self.codeql_sarif_path}")
77+
78+
# Packages are allowed to specify package-specific query specifiers
79+
# in the package CI YAML file that override the global query specifier.
80+
audit_only = False
81+
query_specifiers = None
82+
package_config_file = Path(os.path.join(
83+
self.package_path, self.package + ".ci.yaml"))
84+
plugin_data = None
85+
if package_config_file.is_file():
86+
with open(package_config_file, 'r') as cf:
87+
package_config_file_data = yaml.safe_load(cf)
88+
if "CodeQlAnalyze" in package_config_file_data:
89+
plugin_data = package_config_file_data["CodeQlAnalyze"]
90+
if "AuditOnly" in plugin_data:
91+
audit_only = plugin_data["AuditOnly"]
92+
if "QuerySpecifiers" in plugin_data:
93+
logging.debug(f"Loading CodeQL query specifiers in "
94+
f"{str(package_config_file)}")
95+
query_specifiers = plugin_data["QuerySpecifiers"]
96+
97+
global_audit_only = builder.env.GetValue("STUART_CODEQL_AUDIT_ONLY")
98+
if global_audit_only:
99+
if global_audit_only.strip().lower() == "true":
100+
audit_only = True
101+
102+
if audit_only:
103+
logging.info(f"CodeQL Analyze plugin is in audit only mode for "
104+
f"{self.package} ({self.target}).")
105+
106+
# Builds can override the query specifiers defined in this plugin
107+
# by setting the value in the STUART_CODEQL_QUERY_SPECIFIERS
108+
# environment variable.
109+
if not query_specifiers:
110+
query_specifiers = builder.env.GetValue(
111+
"STUART_CODEQL_QUERY_SPECIFIERS")
112+
113+
# Use this plugins query set file as the default fallback if it is
114+
# not overridden. It is possible the file is not present if modified
115+
# locally. In that case, skip the plugin.
116+
plugin_query_set = Path(Path(__file__).parent, "CodeQlQueries.qls")
117+
118+
if not query_specifiers and plugin_query_set.is_file():
119+
query_specifiers = str(plugin_query_set.resolve())
120+
121+
if not query_specifiers:
122+
logging.warning("Skipping CodeQL analysis since no CodeQL query "
123+
"specifiers were provided.")
124+
return 0
125+
126+
codeql_params = (f'database analyze {self.codeql_db_path} '
127+
f'{query_specifiers} --format=sarifv2.1.0 '
128+
f'--output={self.codeql_sarif_path} --download '
129+
f'--threads=0')
130+
131+
# CodeQL requires the sarif file parent directory to exist already.
132+
Path(self.codeql_sarif_path).parent.mkdir(exist_ok=True, parents=True)
133+
134+
cmd_ret = RunCmd(self.codeql_path, codeql_params)
135+
if cmd_ret != 0:
136+
logging.critical(f"CodeQL CLI analysis failed with return code "
137+
f"{cmd_ret}.")
138+
139+
if not os.path.isfile(self.codeql_sarif_path):
140+
logging.critical(f"The sarif file {self.codeql_sarif_path} was "
141+
f"not created. Analysis cannot continue.")
142+
return -1
143+
144+
filter_pattern_data = []
145+
global_filter_file_value = builder.env.GetValue(
146+
"STUART_CODEQL_FILTER_FILES")
147+
if global_filter_file_value:
148+
global_filter_files = global_filter_file_value.strip().split(',')
149+
global_filter_files = [Path(f) for f in global_filter_files]
150+
151+
for global_filter_file in global_filter_files:
152+
if global_filter_file.is_file():
153+
with open(global_filter_file, 'r') as ff:
154+
global_filter_file_data = yaml.safe_load(ff)
155+
if "Filters" in global_filter_file_data:
156+
current_pattern_data = \
157+
global_filter_file_data["Filters"]
158+
if type(current_pattern_data) is not list:
159+
logging.critical(
160+
f"CodeQL pattern data must be a list of "
161+
f"strings. Data in "
162+
f"{str(global_filter_file.resolve())} is "
163+
f"invalid. CodeQL analysis is incomplete.")
164+
return -1
165+
filter_pattern_data += current_pattern_data
166+
else:
167+
logging.critical(
168+
f"CodeQL global filter file "
169+
f"{str(global_filter_file.resolve())} is "
170+
f"malformed. Missing Filters section. CodeQL "
171+
f"analysis is incomplete.")
172+
return -1
173+
else:
174+
logging.critical(
175+
f"CodeQL global filter file "
176+
f"{str(global_filter_file.resolve())} was not found. "
177+
f"CodeQL analysis is incomplete.")
178+
return -1
179+
180+
if plugin_data and "Filters" in plugin_data:
181+
if type(plugin_data["Filters"]) is not list:
182+
logging.critical(
183+
"CodeQL pattern data must be a list of strings. "
184+
"CodeQL analysis is incomplete.")
185+
return -1
186+
filter_pattern_data.extend(plugin_data["Filters"])
187+
188+
if filter_pattern_data:
189+
logging.info("Applying CodeQL SARIF result filters.")
190+
analyze_filter.filter_sarif(
191+
self.codeql_sarif_path,
192+
self.codeql_sarif_path,
193+
filter_pattern_data,
194+
split_lines=False)
195+
196+
with open(self.codeql_sarif_path, 'r') as sf:
197+
sarif_file_data = json.load(sf)
198+
199+
try:
200+
# Perform minimal JSON parsing to find the number of errors.
201+
total_errors = 0
202+
for run in sarif_file_data['runs']:
203+
total_errors += len(run['results'])
204+
except KeyError:
205+
logging.critical("Sarif file does not contain expected data. "
206+
"Analysis cannot continue.")
207+
return -1
208+
209+
if total_errors > 0:
210+
if audit_only:
211+
# Show a warning message so CodeQL analysis is not forgotten.
212+
# If the repo owners truly do not want to fix CodeQL issues,
213+
# analysis should be disabled entirely.
214+
logging.warning(f"{self.package} ({self.target}) CodeQL "
215+
f"analysis ignored {total_errors} errors due "
216+
f"to audit mode being enabled.")
217+
return 0
218+
else:
219+
logging.error(f"{self.package} ({self.target}) CodeQL "
220+
f"analysis failed with {total_errors} errors.")
221+
222+
return total_errors
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## @file CodeQlAnalyze_plug_in.py
2+
#
3+
# Build plugin used to analyze CodeQL results.
4+
#
5+
# Copyright (c) Microsoft Corporation. All rights reserved.
6+
# SPDX-License-Identifier: BSD-2-Clause-Patent
7+
##
8+
9+
{
10+
"scope": "codeql-analyze",
11+
"name": "CodeQL Analyze Plugin",
12+
"module": "CodeQlAnalyzePlugin"
13+
}

0 commit comments

Comments
 (0)