Skip to content

Commit

Permalink
Add CodeQL helper functions
Browse files Browse the repository at this point in the history
Adds a new file that contains functions are intended to ease repo
integration with CodeQL, ensure consistent command-line usage across
repos, and define standard scopes that other plugins and tools can
depend on for CodeQL operations.

Signed-off-by: Michael Kubacki <[email protected]>
  • Loading branch information
makubacki committed Sep 6, 2023
1 parent 1c397b5 commit 70b696b
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 0 deletions.
110 changes: 110 additions & 0 deletions docs/user/features/codeql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# CodeQL

## About CodeQL

CodeQL is open source and free for open-source projects. It is maintained by GitHub and naturally has excellent
integration with GitHub projects. CodeQL generates a "database" during the firmware build process that enables queries
to run against that database. Many open-source queries are officially supported and comprise the vulnerability analysis
performed against the database. These queries are maintained here - [github/codeql](https://github.com/github/codeql).

Queries are written in an object-oriented query language called QL. CodeQL provides:

1. A [command-line (CLI) interface](https://codeql.github.com/docs/codeql-cli/#codeql-cli)
2. A [VS Code extension](https://codeql.github.com/docs/codeql-for-visual-studio-code/#codeql-for-visual-studio-code)
to help write queries and run queries
3. [GitHub action](https://github.com/github/codeql-action) support for repo integration via
[code scanning](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning)
4. In addition to other features described in the [CodeQL overview](https://codeql.github.com/docs/codeql-overview/)

## CodeQL Usage

CodeQL provides the capability to debug the actual queries and for our (Tianocore) community to write our own queries
and even contribute back to the upstream repo when appropriate. In other cases, we might choose to keep our own queries
in a separate TianoCore repo or within a directory in the edk2 code tree.

This is all part of CodeQL Scanning. [This page](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning)
has information concerning how to configure CodeQL scanning within a GitHub project such as edk2. Information on the
topic of running additional custom queries is documented [here](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#running-additional-queries)
in that page.

In addition, CodeQL offers the flexibility to:

- Build databases locally
- Retrieve databases from server builds
- Relatively quickly test queries locally against a database for a fast feedback loop
- Suppress false positives
- Customize the files and queries used in the edk2 project and quickly keep this list in sync between the server and
local execution

## EDK2 PyTool Extensions CodeQL Module

A Python module located in `edk2toolext/codeql.py` provides helper functions that platform and CI build files can use
to support CodeQL enabled builds with minimal effort.

The following functions are available. The expected actions to be taken per scope are included as well.

- `add_command_line_option()` - Adds the CodeQL command (`--codeql`) to the platform command line options.
- `get_scopes()` - Returns the active CodeQL scopes for this build.
- Host OS neutral scopes:
- `codeql-build` - Build the CodeQL database during firmware build.
- `codeql-analyze` - Analyze the CodeQL database after firmware build (post-build).
- Linux scope:
- `codeql-linux-ext-dep` - Download the Linux CodeQL CLI external dependency.
- Windows scope:
- `codeql-windows-ext-dep` - Download the Windows CodeQL CLI external dependency.
- `is_codeql_enabled_on_command_line` - Returns whether CodeQL was enabled (via `--codeql`) on the command line.
- `set_audit_only_mode` - Configures the CodeQL plugin to run in audit only mode.

These functions are intended to ease repo integration with CodeQL, ensure consistent command-line usage across repos,
and define standard scopes that other plugins and tools can depend on for CodeQL operations.

### Integration Examples

This section provides examples of how to use the functions available in `edk2toolext/codeql.py`.

#### add_common_line_option()

Call when the command-line options are registered for the build:

```python
import edk2toolext.codeql as codeql

def AddCommandLineOptions(self, parserObj):
codeql_helpers.add_command_line_option(parserObj)
```

#### get_scopes()

Call when scopes are returned for the build file:

```python
def GetActiveScopes(self):
scopes = ("cibuild", "edk2-build", "host-based-test")

scopes += codeql_helpers.get_scopes(self.codeql)
return scopes
```

#### is_codeql_enabled_on_command_line()

Call when code needs to know if the CodeQL argument was given on the command line:

```python
def RetrieveCommandLineOptions(self, args):
super().RetrieveCommandLineOptions(args)

try:
self.codeql = codeql_helpers.is_codeql_enabled_on_command_line(args)
```

In the above example, a `Settings` class is creating an instance variable that can be referred to later based on the
value returned from the function.

#### set_audit_only_mode()

Call when a CI or platform build would like to enable audit mode for the current build invocation.

```python
def GetActiveScopes(self):
set_audit_only_mode()
```
84 changes: 84 additions & 0 deletions edk2toolext/codeql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# @file codeql.py
#
# Exports functions commonly needed for Stuart-based platforms to easily
# enable CodeQL in their platform build.
#
# Copyright (c) Microsoft Corporation. All rights reserved.
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
"""CodeQL Helper Functions.
Contains functions are intended to ease repo integration with CodeQL, ensure
consistent command-line usage across repos, and define standard scopes that
other plugins and tools can depend on for CodeQL operations.
"""
from argparse import ArgumentParser, Namespace
from typing import Tuple

from edk2toollib.utility_functions import GetHostInfo

from edk2toolext.environment.uefi_build import UefiBuilder


def add_command_line_option(parser: ArgumentParser) -> None:
"""Add the CodeQL command to the platform command line options.
Args:
parser (ArgumentParser): The argument parser used in this build.
"""
parser.add_argument(
'--codeql',
dest='codeql',
action='store_true',
default=False,
help="Optional - Produces CodeQL results from the build.")


def get_scopes(codeql_enabled: bool) -> Tuple[str]:
"""Return the active CodeQL scopes for this build.
Args:
codeql_enabled (bool): Whether CodeQL is enabled.
Returns:
Tuple[str]: A tuple of strings containing scopes that enable the
CodeQL plugin.
"""
active_scopes = ()

if codeql_enabled:
if GetHostInfo().os == "Linux":
active_scopes += ("codeql-linux-ext-dep",)
else:
active_scopes += ("codeql-windows-ext-dep",)
active_scopes += ("codeql-build", "codeql-analyze")

return active_scopes


def is_codeql_enabled_on_command_line(args: Namespace) -> bool:
"""Return whether CodeQL was enabled on the command line.
Args:
args (Namespace): Object holding a string representation of command
line arguments.
Returns:
bool: True if CodeQL is enabled on the command line. Otherwise, false.
"""
return args.codeql


def set_audit_only_mode(uefi_builder: UefiBuilder) -> None:
"""Configure the CodeQL plugin to run in audit only mode.
Args:
uefi_builder (UefiBuilder): The UefiBuilder object for this platform
build.
"""
uefi_builder.env.SetValue(
"STUART_CODEQL_AUDIT_ONLY",
"true",
"Platform Defined")
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ nav:
- Self Describing Environment: features/sde.md
- Settings Manager: features/settings_manager.md
- Logging: features/logging.md
- CodeQL: features/codeql.md
- WSL: features/using_linux.md
- API Reference:
- ... | flat | api/**/*.md
78 changes: 78 additions & 0 deletions tests.unit/test_codeql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# @file utility_functions_test.py
# unit test for utility_functions module.
#
#
# Copyright (c) Microsoft Corporation
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
"""CodeQL module unit tests."""

import unittest
from argparse import ArgumentParser, Namespace
from unittest.mock import Mock, patch

import edk2toolext.codeql as codeql


class CodeQlTests(unittest.TestCase):
"""CodeQL unit tests."""

def test_codeql_option(self):
"""Tests that common line options are added as expected."""
parser = ArgumentParser()
codeql.add_command_line_option(parser)

# Test that the default value is False
args = parser.parse_args([])
self.assertFalse(args.codeql)

# Test that the option sets the value to True
args_with_option = parser.parse_args(['--codeql'])
self.assertTrue(args_with_option.codeql)

@patch('edk2toolext.codeql.GetHostInfo')
def test_codeql_enabled_linux(self, mock_host_info):
"""Tests that the proper scope is returned on a Linux host."""
mock_host_info.return_value.os = "Linux"
result = codeql.get_scopes(codeql_enabled=True)
expected_result = ("codeql-linux-ext-dep", "codeql-build",
"codeql-analyze")
self.assertEqual(result, expected_result)

@patch('edk2toolext.codeql.GetHostInfo')
def test_codeql_enabled_windows(self, mock_host_info):
"""Tests that the proper scope is returned on a Windows host."""
mock_host_info.return_value.os = "Windows"
result = codeql.get_scopes(codeql_enabled=True)
expected_result = ("codeql-windows-ext-dep", "codeql-build",
"codeql-analyze")
self.assertEqual(result, expected_result)

@patch('edk2toolext.codeql.GetHostInfo')
def test_codeql_disabled(self, mock_host_info):
"""Tests that the proper scopes are returned if CodeQL is disabled."""
result = codeql.get_scopes(codeql_enabled=False)
expected_result = ()
self.assertEqual(result, expected_result)

def test_codeql_enabled(self):
"""Tests that CodeQL is recognized on the command-line properly."""
mock_args = Namespace(codeql=True)
result = codeql.is_codeql_enabled_on_command_line(mock_args)
self.assertTrue(result)

def test_codeql_not_enabled(self):
"""Tests that CodeQL is recognized on the command-line properly."""
mock_args = Namespace(codeql=False)
result = codeql.is_codeql_enabled_on_command_line(mock_args)
self.assertFalse(result)

def test_set_audit_only_mode(self):
"""Tests that CodeQL audit mode is enabled as expected."""
mock_uefi_builder = Mock()
codeql.set_audit_only_mode(mock_uefi_builder)
mock_uefi_builder.env.SetValue.assert_called_once_with(
"STUART_CODEQL_AUDIT_ONLY",
"true",
"Platform Defined")

0 comments on commit 70b696b

Please sign in to comment.