mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge branch 'main' into js/no-type-extraction
This commit is contained in:
4
.github/copilot-instructions.md
vendored
Normal file
4
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
When reviewing code:
|
||||
* do not review changes in files with `.expected` extension (they are automatically ensured to be correct).
|
||||
* in `.ql` and `.qll` files, do not try to review the code itself as you don't understand the programming language
|
||||
well enough to make comments in these languages. You can still check for typos or comment improvements.
|
||||
1
.github/workflows/check-change-note.yml
vendored
1
.github/workflows/check-change-note.yml
vendored
@@ -16,7 +16,6 @@ on:
|
||||
- "shared/**/*.qll"
|
||||
- "!**/experimental/**"
|
||||
- "!ql/**"
|
||||
- "!rust/**"
|
||||
- ".github/workflows/check-change-note.yml"
|
||||
|
||||
jobs:
|
||||
|
||||
23
.github/workflows/check-overlay-annotations.yml
vendored
Normal file
23
.github/workflows/check-overlay-annotations.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Check overlay annotations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check overlay annotations
|
||||
run: python config/add-overlay-annotations.py --check java
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
- name: Create database
|
||||
run: |
|
||||
"${CODEQL}" database create \
|
||||
--search-path "${{ github.workspace }}"
|
||||
--search-path "${{ github.workspace }}" \
|
||||
--threads 4 \
|
||||
--language ql --source-root "${{ github.workspace }}/repo" \
|
||||
"${{ runner.temp }}/database"
|
||||
|
||||
@@ -9,7 +9,7 @@ repos:
|
||||
- id: trailing-whitespace
|
||||
exclude: /test/.*$(?<!\.qlref)|.*\.patch$|.*\.qll?$
|
||||
- id: end-of-file-fixer
|
||||
exclude: /test/.*$(?<!\.qlref)|.*\.patch$|.*\.qll?$
|
||||
exclude: Cargo.lock$|/test/.*$(?<!\.qlref)|.*\.patch$|.*\.qll?$
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v17.0.6
|
||||
|
||||
919
Cargo.lock
generated
919
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,3 @@ members = [
|
||||
"rust/autobuild",
|
||||
]
|
||||
exclude = ["mad-generation-build"]
|
||||
|
||||
[patch.crates-io]
|
||||
# patch for build script bug preventing bazel build
|
||||
# see https://github.com/rust-lang/rustc_apfloat/pull/17
|
||||
rustc_apfloat = { git = "https://github.com/redsun82/rustc_apfloat.git", rev = "32968f16ef1b082243f9bf43a3fbd65c381b3e27" }
|
||||
|
||||
54
MODULE.bazel
54
MODULE.bazel
@@ -37,7 +37,7 @@ bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True
|
||||
# the versions there are canonical, the versions here are used for CI in github/codeql, as well as for the vendoring of dependencies.
|
||||
RUST_EDITION = "2024"
|
||||
|
||||
RUST_VERSION = "1.85.0"
|
||||
RUST_VERSION = "1.86.0"
|
||||
|
||||
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
|
||||
rust.toolchain(
|
||||
@@ -71,11 +71,11 @@ use_repo(
|
||||
tree_sitter_extractors_deps = use_extension("//misc/bazel/3rdparty:tree_sitter_extractors_extension.bzl", "r")
|
||||
use_repo(
|
||||
tree_sitter_extractors_deps,
|
||||
"vendor_ts__anyhow-1.0.97",
|
||||
"vendor_ts__anyhow-1.0.98",
|
||||
"vendor_ts__argfile-0.2.1",
|
||||
"vendor_ts__chalk-ir-0.100.0",
|
||||
"vendor_ts__chrono-0.4.40",
|
||||
"vendor_ts__clap-4.5.35",
|
||||
"vendor_ts__chalk-ir-0.103.0",
|
||||
"vendor_ts__chrono-0.4.41",
|
||||
"vendor_ts__clap-4.5.40",
|
||||
"vendor_ts__dunce-1.0.5",
|
||||
"vendor_ts__either-1.15.0",
|
||||
"vendor_ts__encoding-0.2.33",
|
||||
@@ -87,33 +87,33 @@ use_repo(
|
||||
"vendor_ts__lazy_static-1.5.0",
|
||||
"vendor_ts__mustache-0.9.0",
|
||||
"vendor_ts__num-traits-0.2.19",
|
||||
"vendor_ts__num_cpus-1.16.0",
|
||||
"vendor_ts__proc-macro2-1.0.94",
|
||||
"vendor_ts__num_cpus-1.17.0",
|
||||
"vendor_ts__proc-macro2-1.0.95",
|
||||
"vendor_ts__quote-1.0.40",
|
||||
"vendor_ts__ra_ap_base_db-0.0.273",
|
||||
"vendor_ts__ra_ap_cfg-0.0.273",
|
||||
"vendor_ts__ra_ap_hir-0.0.273",
|
||||
"vendor_ts__ra_ap_hir_def-0.0.273",
|
||||
"vendor_ts__ra_ap_hir_expand-0.0.273",
|
||||
"vendor_ts__ra_ap_hir_ty-0.0.273",
|
||||
"vendor_ts__ra_ap_ide_db-0.0.273",
|
||||
"vendor_ts__ra_ap_intern-0.0.273",
|
||||
"vendor_ts__ra_ap_load-cargo-0.0.273",
|
||||
"vendor_ts__ra_ap_parser-0.0.273",
|
||||
"vendor_ts__ra_ap_paths-0.0.273",
|
||||
"vendor_ts__ra_ap_project_model-0.0.273",
|
||||
"vendor_ts__ra_ap_span-0.0.273",
|
||||
"vendor_ts__ra_ap_stdx-0.0.273",
|
||||
"vendor_ts__ra_ap_syntax-0.0.273",
|
||||
"vendor_ts__ra_ap_vfs-0.0.273",
|
||||
"vendor_ts__rand-0.9.0",
|
||||
"vendor_ts__ra_ap_base_db-0.0.288",
|
||||
"vendor_ts__ra_ap_cfg-0.0.288",
|
||||
"vendor_ts__ra_ap_hir-0.0.288",
|
||||
"vendor_ts__ra_ap_hir_def-0.0.288",
|
||||
"vendor_ts__ra_ap_hir_expand-0.0.288",
|
||||
"vendor_ts__ra_ap_hir_ty-0.0.288",
|
||||
"vendor_ts__ra_ap_ide_db-0.0.288",
|
||||
"vendor_ts__ra_ap_intern-0.0.288",
|
||||
"vendor_ts__ra_ap_load-cargo-0.0.288",
|
||||
"vendor_ts__ra_ap_parser-0.0.288",
|
||||
"vendor_ts__ra_ap_paths-0.0.288",
|
||||
"vendor_ts__ra_ap_project_model-0.0.288",
|
||||
"vendor_ts__ra_ap_span-0.0.288",
|
||||
"vendor_ts__ra_ap_stdx-0.0.288",
|
||||
"vendor_ts__ra_ap_syntax-0.0.288",
|
||||
"vendor_ts__ra_ap_vfs-0.0.288",
|
||||
"vendor_ts__rand-0.9.1",
|
||||
"vendor_ts__rayon-1.10.0",
|
||||
"vendor_ts__regex-1.11.1",
|
||||
"vendor_ts__serde-1.0.219",
|
||||
"vendor_ts__serde_json-1.0.140",
|
||||
"vendor_ts__serde_with-3.12.0",
|
||||
"vendor_ts__syn-2.0.100",
|
||||
"vendor_ts__toml-0.8.20",
|
||||
"vendor_ts__serde_with-3.13.0",
|
||||
"vendor_ts__syn-2.0.103",
|
||||
"vendor_ts__toml-0.8.23",
|
||||
"vendor_ts__tracing-0.1.41",
|
||||
"vendor_ts__tracing-flame-0.2.0",
|
||||
"vendor_ts__tracing-subscriber-0.3.19",
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.4.12
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Fixed performance issues in the parsing of Bash scripts in workflow files,
|
||||
which led to out-of-disk errors when analysing certain workflow files with
|
||||
complex interpolations of shell commands or quoted strings.
|
||||
|
||||
## 0.4.11
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.4.12
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Fixed performance issues in the parsing of Bash scripts in workflow files,
|
||||
which led to out-of-disk errors when analysing certain workflow files with
|
||||
complex interpolations of shell commands or quoted strings.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.4.11
|
||||
lastReleaseVersion: 0.4.12
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/actions-all
|
||||
version: 0.4.12-dev
|
||||
version: 0.4.13-dev
|
||||
library: true
|
||||
warnOnImplicitThis: true
|
||||
dependencies:
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.6.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.6.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# Environment Path Injection
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Actions allow to define the system PATH variable by writing to a file pointed to by the `GITHUB_PATH` environment variable. Writing to this file appends a directory to the system PATH variable and automatically makes it available to all subsequent actions in the current job.
|
||||
|
||||
@@ -12,11 +10,11 @@ echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
If an attacker can control the contents of the system PATH, they are able to influence what commands are run in subsequent steps of the same job.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Do not allow untrusted data to influence the system PATH: Avoid using untrusted data sources (e.g., artifact content) to define the system PATH.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -36,4 +34,4 @@ If an attacker can manipulate the value being set, such as through artifact down
|
||||
|
||||
## References
|
||||
|
||||
- [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)
|
||||
- GitHub Docs: [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# Environment Path Injection
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Actions allow to define the system PATH variable by writing to a file pointed to by the `GITHUB_PATH` environment variable. Writing to this file appends a directory to the system PATH variable and automatically makes it available to all subsequent actions in the current job.
|
||||
|
||||
@@ -12,11 +10,11 @@ echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
If an attacker can control the contents of the system PATH, they are able to influence what commands are run in subsequent steps of the same job.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Do not allow untrusted data to influence the system PATH: Avoid using untrusted data sources (e.g., artifact content) to define the system PATH.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -36,4 +34,4 @@ If an attacker can manipulate the value being set, such as through artifact down
|
||||
|
||||
## References
|
||||
|
||||
- [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)
|
||||
- GitHub Docs: [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# Environment Variable Injection
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Actions allow to define environment variables by writing to a file pointed to by the `GITHUB_ENV` environment variable:
|
||||
|
||||
@@ -37,7 +35,7 @@ steps:
|
||||
|
||||
If an attacker can control the values assigned to environment variables and there is no sanitization in place, the attacker will be able to inject additional variables by injecting new lines or `{delimiters}`.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
1. **Do not allow untrusted data to influence environment variables**:
|
||||
|
||||
@@ -64,7 +62,7 @@ If an attacker can control the values assigned to environment variables and ther
|
||||
} >> "$GITHUB_ENV"
|
||||
```
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Example of Vulnerability
|
||||
|
||||
@@ -113,5 +111,5 @@ An attacker is be able to run arbitrary code by injecting environment variables
|
||||
|
||||
## References
|
||||
|
||||
- [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)
|
||||
- [GitHub Actions Exploitation: Repo Jacking and Environment Manipulation](https://www.synacktiv.com/publications/github-actions-exploitation-repo-jacking-and-environment-manipulation)
|
||||
- GitHub Docs: [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions).
|
||||
- Synacktiv: [GitHub Actions Exploitation: Repo Jacking and Environment Manipulation](https://www.synacktiv.com/publications/github-actions-exploitation-repo-jacking-and-environment-manipulation).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# Environment Variable Injection
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Actions allow to define environment variables by writing to a file pointed to by the `GITHUB_ENV` environment variable:
|
||||
|
||||
@@ -37,7 +35,7 @@ steps:
|
||||
|
||||
If an attacker can control the values assigned to environment variables and there is no sanitization in place, the attacker will be able to inject additional variables by injecting new lines or `{delimiters}`.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
1. **Do not allow untrusted data to influence environment variables**:
|
||||
|
||||
@@ -64,7 +62,7 @@ If an attacker can control the values assigned to environment variables and ther
|
||||
} >> "$GITHUB_ENV"
|
||||
```
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Example of Vulnerability
|
||||
|
||||
@@ -113,5 +111,5 @@ An attacker would be able to run arbitrary code by injecting environment variabl
|
||||
|
||||
## References
|
||||
|
||||
- [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)
|
||||
- [GitHub Actions Exploitation: Repo Jacking and Environment Manipulation](https://www.synacktiv.com/publications/github-actions-exploitation-repo-jacking-and-environment-manipulation)
|
||||
- GitHub Docs: [Workflow commands for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions).
|
||||
- Synacktiv: [GitHub Actions Exploitation: Repo Jacking and Environment Manipulation](https://www.synacktiv.com/publications/github-actions-exploitation-repo-jacking-and-environment-manipulation).
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
# Code Injection in GitHub Actions
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Using user-controlled input in GitHub Actions may lead to code injection in contexts like _run:_ or _script:_.
|
||||
|
||||
Code injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token may have write access to the repository, allowing an attacker to make changes to the repository.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
The best practice to avoid code injection vulnerabilities in GitHub workflows is to set the untrusted input value of the expression to an intermediate environment variable and then use the environment variable using the native syntax of the shell/script interpreter (that is, not _${{ env.VAR }}_).
|
||||
|
||||
It is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
# Code Injection in GitHub Actions
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Using user-controlled input in GitHub Actions may lead to code injection in contexts like _run:_ or _script:_.
|
||||
|
||||
Code injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token may have write access to the repository, allowing an attacker to make changes to the repository.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
The best practice to avoid code injection vulnerabilities in GitHub workflows is to set the untrusted input value of the expression to an intermediate environment variable and then use the environment variable using the native syntax of the shell/script interpreter (that is, not _${{ env.VAR }}_).
|
||||
|
||||
It is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
# Use of Actions with known vulnerabilities
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
The security of the workflow and the repository could be compromised by GitHub Actions workflows that utilize GitHub Actions with known vulnerabilities.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Either remove the component from the workflow or upgrade it to a version that is not vulnerable.
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Docs: Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot)
|
||||
- GitHub Docs: [Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot).
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
# Actions Job and Workflow Permissions are not set
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
If a GitHub Actions job or workflow has no explicit permissions set, then the repository permissions are used. Repositories created under organizations inherit the organization permissions. The organizations or repositories created before February 2023 have the default permissions set to read-write. Often these permissions do not adhere to the principle of least privilege and can be reduced to read-only, leaving the `write` permission only to a specific types as `issues: write` or `pull-requests: write`.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Add the `permissions` key to the job or the root of workflow (in this case it is applied to all jobs in the workflow that do not have their own `permissions` key) and assign the least privileges required to complete the task:
|
||||
Add the `permissions` key to the job or the root of workflow (in this case it is applied to all jobs in the workflow that do not have their own `permissions` key) and assign the least privileges required to complete the task.
|
||||
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
```yaml
|
||||
name: "My workflow"
|
||||
# No permissions block
|
||||
```
|
||||
|
||||
### Correct Usage
|
||||
|
||||
```yaml
|
||||
name: "My workflow"
|
||||
@@ -27,4 +36,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [Assigning permissions to jobs](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/assigning-permissions-to-jobs)
|
||||
- GitHub Docs: [Assigning permissions to jobs](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/assigning-permissions-to-jobs).
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# Improper Access Control
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Sometimes labels are used to approve GitHub Actions. An authorization check may not be properly implemented, allowing an attacker to mutate the code after it has been reviewed and approved by label.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
When using labels, make sure that the code cannot be modified after it has been reviewed and the label has been set.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -57,4 +55,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target)
|
||||
- GitHub Docs: [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target).
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# Excessive Secrets Exposure
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
When the workflow runner cannot determine what secrets are needed to run the workflow, it will pass all the available secrets to the runner including organization and repository secrets. This violates the least privileged principle and increases the impact of a potential vulnerability affecting the workflow.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Only pass those secrets that are needed by the workflow. Avoid using expressions such as `toJSON(secrets)` or dynamically accessed secrets such as `secrets[format('GH_PAT_%s', matrix.env)]` since the workflow will need to receive all secrets to decide at runtime which one needs to be used.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -48,5 +46,5 @@ env:
|
||||
|
||||
## References
|
||||
|
||||
- [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-encrypted-secrets-in-a-workflow)
|
||||
- [Job uses all secrets](https://github.com/boostsecurityio/poutine/blob/main/docs/content/en/rules/job_all_secrets.md)
|
||||
- GitHub Docs: [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-encrypted-secrets-in-a-workflow).
|
||||
- poutine: [Job uses all secrets](https://github.com/boostsecurityio/poutine/blob/main/docs/content/en/rules/job_all_secrets.md).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# Storage of sensitive information in GitHub Actions artifact
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Sensitive information included in a GitHub Actions artifact can allow an attacker to access the sensitive information if the artifact is published.
|
||||
|
||||
@@ -10,6 +8,8 @@ Only store information that is meant to be publicly available in a GitHub Action
|
||||
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
The following example uses `actions/checkout` to checkout code which stores the GITHUB_TOKEN in the \`.git/config\` file and then stores the contents of the \`.git\` repository into the artifact:
|
||||
|
||||
```yaml
|
||||
@@ -28,6 +28,8 @@ jobs:
|
||||
path: .
|
||||
```
|
||||
|
||||
### Correct Usage
|
||||
|
||||
The issue has been fixed below, where the `actions/upload-artifact` uses a version (v4+) which does not include hidden files or directories into the artifact.
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# Unmasked Secret Exposure
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Secrets derived from other secrets are not known to the workflow runner, and therefore are not masked unless explicitly registered.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Avoid defining non-plain secrets. For example, do not define a new secret containing a JSON object and then read properties out of it from the workflow, since these read values will not be masked by the workflow runner.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -34,4 +32,4 @@ Avoid defining non-plain secrets. For example, do not define a new secret contai
|
||||
|
||||
## References
|
||||
|
||||
- [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-encrypted-secrets-in-a-workflow)
|
||||
- GitHub Docs: [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#using-encrypted-secrets-in-a-workflow).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# Cache Poisoning in GitHub Actions
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache from unprivileged workflow, potentially leading to code execution in privileged workflows.
|
||||
|
||||
@@ -23,7 +21,7 @@ In GitHub Actions, cache scopes are primarily determined by the branch structure
|
||||
|
||||
Due to the above design, if something is cached in the context of the default branch (e.g., `main`), it becomes accessible to any feature branch derived from `main`.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
1. Avoid using caching in workflows that handle sensitive operations like releases.
|
||||
2. If caching must be used:
|
||||
@@ -34,7 +32,7 @@ Due to the above design, if something is cached in the context of the default br
|
||||
4. Never run untrusted code in the context of the default branch.
|
||||
5. Sign the cache value cryptographically and verify the signature before usage.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -78,6 +76,6 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/)
|
||||
- [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
|
||||
- [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/)
|
||||
- Adnan Khan's Blog: [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/).
|
||||
- GitHub Docs: [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows).
|
||||
- Scribe Security Blog: [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# Cache Poisoning in GitHub Actions
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache from unprivileged workflow, potentially leading to code execution in privileged workflows.
|
||||
|
||||
@@ -23,7 +21,7 @@ In GitHub Actions, cache scopes are primarily determined by the branch structure
|
||||
|
||||
Due to the above design, if something is cached in the context of the default branch (e.g., `main`), it becomes accessible to any feature branch derived from `main`.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
1. Avoid using caching in workflows that handle sensitive operations like releases.
|
||||
2. If caching must be used:
|
||||
@@ -34,7 +32,7 @@ Due to the above design, if something is cached in the context of the default br
|
||||
4. Never run untrusted code in the context of the default branch.
|
||||
5. Sign the cache value cryptographically and verify the signature before usage.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -123,6 +121,6 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/)
|
||||
- [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
|
||||
- [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/)
|
||||
- Adnan Khan's Blog: [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/).
|
||||
- GitHub Docs: [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows).
|
||||
- Scribe Security Blog: [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# Cache Poisoning in GitHub Actions
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Actions cache poisoning is a technique that allows an attacker to inject malicious content into the Action's cache from unprivileged workflow, potentially leading to code execution in privileged workflows.
|
||||
|
||||
@@ -23,7 +21,7 @@ In GitHub Actions, cache scopes are primarily determined by the branch structure
|
||||
|
||||
Due to the above design, if something is cached in the context of the default branch (e.g., `main`), it becomes accessible to any feature branch derived from `main`.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
1. Avoid using caching in workflows that handle sensitive operations like releases.
|
||||
2. If caching must be used:
|
||||
@@ -34,7 +32,7 @@ Due to the above design, if something is cached in the context of the default br
|
||||
4. Never run untrusted code in the context of the default branch.
|
||||
5. Sign the cache value cryptographically and verify the signature before usage.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -80,6 +78,6 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/)
|
||||
- [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)
|
||||
- [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/)
|
||||
- Adnan Khan's Blog: [The Monsters in Your Build Cache – GitHub Actions Cache Poisoning](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/).
|
||||
- GitHub Docs: [GitHub Actions Caching Documentation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows).
|
||||
- Scribe Security Blog: [Cache Poisoning in GitHub Actions](https://scribesecurity.com/blog/github-cache-poisoning/).
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
# Untrusted Checkout TOCTOU (Time-of-check to time-of-use)
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Untrusted Checkout is protected by a security check but the checked-out branch can be changed after the check.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Verify that the code has not been modified after the security check. This may be achieved differently depending on the type of check:
|
||||
|
||||
- Deployment Environment Approval: Make sure to use a non-mutable reference to the code to be executed. For example use a `sha` instead of a `ref`.
|
||||
- Label Gates: Make sure to use a non-mutable reference to the code to be executed. For example use a `sha` instead of a `ref`.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage (Deployment Environment Approval)
|
||||
|
||||
@@ -99,4 +97,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [ActionsTOCTOU](https://github.com/AdnaneKhan/ActionsTOCTOU)
|
||||
- [ActionsTOCTOU](https://github.com/AdnaneKhan/ActionsTOCTOU).
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
# Untrusted Checkout TOCTOU (Time-of-check to time-of-use)
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Untrusted Checkout is protected by a security check but the checked-out branch can be changed after the check.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Verify that the code has not been modified after the security check. This may be achieved differently depending on the type of check:
|
||||
|
||||
- Deployment Environment Approval: Make sure to use a non-mutable reference to the code to be executed. For example use a `sha` instead of a `ref`.
|
||||
- Label Gates: Make sure to use a non-mutable reference to the code to be executed. For example use a `sha` instead of a `ref`.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage (Deployment Environment Approval)
|
||||
|
||||
@@ -99,4 +97,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [ActionsTOCTOU](https://github.com/AdnaneKhan/ActionsTOCTOU)
|
||||
- [ActionsTOCTOU](https://github.com/AdnaneKhan/ActionsTOCTOU).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# If Condition Always Evaluates to True
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Workflow Expressions (`${{ ... }}`) used in the `if` condition of jobs or steps must not contain extra characters or spaces. Otherwise, the condition is invariably evaluated to `true`.
|
||||
|
||||
@@ -14,7 +12,7 @@ To avoid the vulnerability where an `if` condition always evaluates to `true`, i
|
||||
2. Avoid multiline or spaced-out conditional expressions that might inadvertently introduce unwanted characters or formatting.
|
||||
3. Test the workflow to ensure the `if` conditions behave as expected under different scenarios.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Correct Usage
|
||||
|
||||
@@ -60,4 +58,4 @@ To avoid the vulnerability where an `if` condition always evaluates to `true`, i
|
||||
|
||||
## References
|
||||
|
||||
- [Expression Always True Github Issue](https://github.com/actions/runner/issues/1173)
|
||||
- GitHub actions/runner Issues: [Expression Always True](https://github.com/actions/runner/issues/1173).
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# If Condition Always Evaluates to True
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub Workflow Expressions (`${{ ... }}`) used in the `if` condition of jobs or steps must not contain extra characters or spaces. Otherwise, the condition is invariably evaluated to `true`.
|
||||
|
||||
@@ -14,7 +12,7 @@ To avoid the vulnerability where an `if` condition always evaluates to `true`, i
|
||||
2. Avoid multiline or spaced-out conditional expressions that might inadvertently introduce unwanted characters or formatting.
|
||||
3. Test the workflow to ensure the `if` conditions behave as expected under different scenarios.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Correct Usage
|
||||
|
||||
@@ -60,4 +58,4 @@ To avoid the vulnerability where an `if` condition always evaluates to `true`, i
|
||||
|
||||
## References
|
||||
|
||||
- [Expression Always True Github Issue](https://github.com/actions/runner/issues/1173)
|
||||
- GitHub actions/runner Issues: [Expression Always True](https://github.com/actions/runner/issues/1173).
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
# Artifact poisoning
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
The workflow downloads artifacts that may be poisoned by an attacker in previously triggered workflows. If the contents of these artifacts are not correctly extracted, stored and verified, they may lead to repository compromise if untrusted code gets executed in a privileged job.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
- Always consider artifacts content as untrusted.
|
||||
- Extract the contents of artifacts to a temporary folder so they cannot override existing files.
|
||||
- Verify the contents of the artifacts downloaded. If an artifact is expected to contain a numeric value, verify it before using it.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -69,4 +67,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
|
||||
- GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/).
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
# Artifact poisoning
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
The workflow downloads artifacts that may be poisoned by an attacker in previously triggered workflows. If the contents of these artifacts are not correctly extracted, stored and verified, they may lead to repository compromise if untrusted code gets executed in a privileged job.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
- Always consider artifacts content as untrusted.
|
||||
- Extract the contents of artifacts to a temporary folder so they cannot override existing files.
|
||||
- Verify the contents of the artifacts downloaded. If an artifact is expected to contain a numeric value, verify it before using it.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -69,4 +67,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
|
||||
- GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/).
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# Unpinned tag for 3rd party Action in workflow
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Using a tag for a 3rd party Action that is not pinned to a commit can lead to executing an untrusted Action through a supply chain attack.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
Pinning an action to a full length commit SHA is currently the only way to use a non-immutable action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. When selecting a SHA, you should verify it is from the action's repository and not a repository fork.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -24,4 +22,4 @@ Pinning an action to a full length commit SHA is currently the only way to use a
|
||||
|
||||
## References
|
||||
|
||||
- [Using third-party actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions)
|
||||
- GitHub Docs: [Using third-party actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions).
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Execution of Untrusted Checked-out Code
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub workflows can be triggered through various repository events, including incoming pull requests (PRs) or comments on Issues/PRs. A potentially dangerous misuse of the triggers such as `pull_request_target` or `issue_comment` followed by an explicit checkout of untrusted code (Pull Request HEAD) may lead to repository compromise if untrusted code gets executed in a privileged job.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
- Avoid using `pull_request_target` unless necessary.
|
||||
- Employ unprivileged `pull_request` workflows followed by `workflow_run` for privileged operations.
|
||||
@@ -14,7 +12,7 @@ The best practice is to handle the potentially untrusted pull request via the **
|
||||
|
||||
The artifacts downloaded from the first workflow should be considered untrusted and must be verified.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -134,4 +132,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
|
||||
- GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/).
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Execution of Untrusted Checked-out Code
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub workflows can be triggered through various repository events, including incoming pull requests (PRs) or comments on Issues/PRs. A potentially dangerous misuse of the triggers such as `pull_request_target` or `issue_comment` followed by an explicit checkout of untrusted code (Pull Request HEAD) may lead to repository compromise if untrusted code gets executed in a privileged job.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
- Avoid using `pull_request_target` unless necessary.
|
||||
- Employ unprivileged `pull_request` workflows followed by `workflow_run` for privileged operations.
|
||||
@@ -14,7 +12,7 @@ The best practice is to handle the potentially untrusted pull request via the **
|
||||
|
||||
The artifacts downloaded from the first workflow should be considered untrusted and must be verified.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -134,4 +132,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
|
||||
- GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/).
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Execution of Untrusted Checked-out Code
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
GitHub workflows can be triggered through various repository events, including incoming pull requests (PRs) or comments on Issues/PRs. A potentially dangerous misuse of the triggers such as `pull_request_target` or `issue_comment` followed by an explicit checkout of untrusted code (Pull Request HEAD) may lead to repository compromise if untrusted code gets executed in a privileged job.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
- Avoid using `pull_request_target` unless necessary.
|
||||
- Employ unprivileged `pull_request` workflows followed by `workflow_run` for privileged operations.
|
||||
@@ -14,7 +12,7 @@ The best practice is to handle the potentially untrusted pull request via the **
|
||||
|
||||
The artifacts downloaded from the first workflow should be considered untrusted and must be verified.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -134,4 +132,4 @@ jobs:
|
||||
|
||||
## References
|
||||
|
||||
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
|
||||
- GitHub Security Lab Research: [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/).
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
# Unneccesary use of advanced configuration
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
The CodeQL workflow does not use any custom settings and could be simplified by switching to the CodeQL default setup.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
If there is no reason to have a custom configuration switch to the CodeQL default setup.
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Docs: Configuring Default Setup for a repository](https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning#configuring-default-setup-for-a-repository)
|
||||
- GitHub Docs: [Configuring Default Setup for a repository](https://docs.github.com/en/code-security/code-scanning/enabling-code-scanning/configuring-default-setup-for-code-scanning#configuring-default-setup-for-a-repository).
|
||||
|
||||
3
actions/ql/src/change-notes/released/0.6.4.md
Normal file
3
actions/ql/src/change-notes/released/0.6.4.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.6.4
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.3
|
||||
lastReleaseVersion: 0.6.4
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
# Argument Injection in GitHub Actions
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Passing user-controlled arguments to certain commands in the context of `Run` steps may lead to arbitrary code execution.
|
||||
|
||||
Argument injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token may have write access to the repository, allowing the attacker to make changes to the repository.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
When possible avoid passing user-controlled data to commands which may spawn new processes using some of their arguments.
|
||||
|
||||
It is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -35,7 +33,7 @@ An attacker may set the body of an Issue comment to `BAR/g;1e whoami;#` and the
|
||||
|
||||
## References
|
||||
|
||||
- [Common Weakness Enumeration: CWE-88](https://cwe.mitre.org/data/definitions/88.html).
|
||||
- [Argument Injection Explained](https://sonarsource.github.io/argument-injection-vectors/explained/)
|
||||
- [Argument Injection Vectors](https://sonarsource.github.io/argument-injection-vectors/)
|
||||
- [GTFOBins](https://gtfobins.github.io/)
|
||||
- Common Weakness Enumeration: [CWE-88](https://cwe.mitre.org/data/definitions/88.html).
|
||||
- [Argument Injection Vectors](https://sonarsource.github.io/argument-injection-vectors/).
|
||||
- Argument Injection Vectors: [Argument Injection Explained](https://sonarsource.github.io/argument-injection-vectors/explained/).
|
||||
- [GTFOBins](https://gtfobins.github.io/).
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
# Argument Injection in GitHub Actions
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
Passing user-controlled arguments to certain commands in the context of `Run` steps may lead to arbitrary code execution.
|
||||
|
||||
Argument injection in GitHub Actions may allow an attacker to exfiltrate any secrets used in the workflow and the temporary GitHub repository authorization token. The token may have write access to the repository, allowing the attacker to make changes to the repository.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
When possible avoid passing user-controlled data to commands which may spawn new processes using some of their arguments.
|
||||
|
||||
It is also recommended to limit the permissions of any tokens used by a workflow such as the GITHUB_TOKEN.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -35,7 +33,7 @@ An attacker may set the body of an Issue comment to `BAR|g;1e whoami;#` and the
|
||||
|
||||
## References
|
||||
|
||||
- [Common Weakness Enumeration: CWE-88](https://cwe.mitre.org/data/definitions/88.html).
|
||||
- [Argument Injection Explained](https://sonarsource.github.io/argument-injection-vectors/explained/)
|
||||
- [Argument Injection Vectors](https://sonarsource.github.io/argument-injection-vectors/)
|
||||
- [GTFOBins](https://gtfobins.github.io/)
|
||||
- Common Weakness Enumeration: [CWE-88](https://cwe.mitre.org/data/definitions/88.html).
|
||||
- [Argument Injection Vectors](https://sonarsource.github.io/argument-injection-vectors/).
|
||||
- Argument Injection Vectors: [Argument Injection Explained](https://sonarsource.github.io/argument-injection-vectors/explained/).
|
||||
- [GTFOBins](https://gtfobins.github.io/).
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# Unversioned Immutable Action
|
||||
|
||||
## Description
|
||||
## Overview
|
||||
|
||||
This action is eligible for Immutable Actions, a new GitHub feature that is currently only available for internal users. Immutable Actions are released as packages in the GitHub package registry instead of resolved from a pinned SHA at the repository. The Immutable Action provides the same immutability as pinning the version to a SHA but with improved readability and additional security guarantees.
|
||||
|
||||
## Recommendations
|
||||
## Recommendation
|
||||
|
||||
For internal users: when using [immutable actions](https://github.com/github/package-registry-team/blob/main/docs/immutable-actions/immutable-actions-howto.md) use the full semantic version of the action. This will ensure that the action is resolved to the exact version stored in the GitHub package registry.
|
||||
|
||||
## Examples
|
||||
## Example
|
||||
|
||||
### Incorrect Usage
|
||||
|
||||
@@ -25,4 +23,4 @@ For internal users: when using [immutable actions](https://github.com/github/pac
|
||||
|
||||
## References
|
||||
|
||||
- [Consuming immutable actions]()
|
||||
- [Consuming immutable actions]().
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/actions-queries
|
||||
version: 0.6.4-dev
|
||||
version: 0.6.5-dev
|
||||
library: false
|
||||
warnOnImplicitThis: true
|
||||
groups: [actions, queries]
|
||||
|
||||
274
config/add-overlay-annotations.py
Normal file
274
config/add-overlay-annotations.py
Normal file
@@ -0,0 +1,274 @@
|
||||
# This script is used to annotate .qll files without any existing overlay annotations
|
||||
# with overlay[local?] and overlay[caller?] annotations. Maintenance of overlay annotations
|
||||
# in annotated files will be handled by QL-for-QL queries.
|
||||
|
||||
# It will walk the directory tree and annotate most .qll files, skipping only
|
||||
# some specific cases (e.g., empty files, files that configure dataflow for queries).
|
||||
|
||||
# The script takes a list of languages and processes the corresponding directories.
|
||||
# If the optional --check argument is provided, the script checks for missing annotations,
|
||||
# but does not modify any files.
|
||||
|
||||
# Usage: python3 add-overlay-annotations.py [--check] <language1> <language2> ...
|
||||
|
||||
# The script will modify the files in place and print the changes made.
|
||||
# The script is designed to be run from the root of the repository.
|
||||
|
||||
#!/usr/bin/python3
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from difflib import context_diff
|
||||
|
||||
OVERLAY_PATTERN = re.compile(r'overlay\[[a-zA-Z?_-]+\]')
|
||||
|
||||
def has_overlay_annotations(lines):
|
||||
'''
|
||||
Check whether the given lines contain any overlay[...] annotations.
|
||||
'''
|
||||
return any(OVERLAY_PATTERN.search(line) for line in lines)
|
||||
|
||||
|
||||
def is_line_comment(line):
|
||||
return line.startswith("//") or (line.startswith("/*") and line.endswith("*/"))
|
||||
|
||||
|
||||
def find_file_level_module_declaration(lines):
|
||||
'''
|
||||
Returns the index of the existing file-level module declaration if one
|
||||
exists. Returns None otherwise.
|
||||
'''
|
||||
comment = False
|
||||
for i, line in enumerate(lines):
|
||||
trimmed = line.strip()
|
||||
|
||||
if is_line_comment(trimmed):
|
||||
continue
|
||||
elif trimmed.startswith("/*"):
|
||||
comment = True
|
||||
elif comment and trimmed.endswith("*/"):
|
||||
comment = False
|
||||
elif not comment and trimmed.endswith("module;"):
|
||||
return i
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def is_file_module_qldoc(i, lines):
|
||||
'''
|
||||
Assuming a qldoc ended on line i, determine if it belongs to the implicit
|
||||
file-level module. If it is followed by another qldoc or imports, then it
|
||||
does and if it is followed by any other non-empty, non-comment lines, then
|
||||
we assume that is a declaration of some kind and the qldoc is attached to
|
||||
that declaration.
|
||||
'''
|
||||
comment = False
|
||||
|
||||
for line in lines[i+1:]:
|
||||
trimmed = line.strip()
|
||||
|
||||
if trimmed.startswith("import ") or trimmed.startswith("private import ") or trimmed.startswith("/**"):
|
||||
return True
|
||||
elif is_line_comment(trimmed) or not trimmed:
|
||||
continue
|
||||
elif trimmed.startswith("/*"):
|
||||
comment = True
|
||||
elif comment and trimmed.endswith("*/"):
|
||||
comment = False
|
||||
elif not comment and trimmed:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def find_file_module_qldoc_declaration(lines):
|
||||
'''
|
||||
Returns the index of last line of the implicit file module qldoc if one
|
||||
exists. Returns None otherwise.
|
||||
'''
|
||||
|
||||
qldoc = False
|
||||
comment = False
|
||||
for i, line in enumerate(lines):
|
||||
trimmed = line.strip()
|
||||
|
||||
if trimmed.startswith("//"):
|
||||
continue
|
||||
elif (qldoc or trimmed.startswith("/**")) and trimmed.endswith("*/"):
|
||||
# a qldoc just ended; determine if it belongs to the implicit file module
|
||||
if is_file_module_qldoc(i, lines):
|
||||
return i
|
||||
else:
|
||||
return None
|
||||
elif trimmed.startswith("/**"):
|
||||
qldoc = True
|
||||
elif trimmed.startswith("/*"):
|
||||
comment = True
|
||||
elif comment and trimmed.endswith("*/"):
|
||||
comment = False
|
||||
elif (not qldoc and not comment) and trimmed:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def only_comments(lines):
|
||||
'''
|
||||
Returns true if the lines contain only comments and empty lines.
|
||||
'''
|
||||
comment = False
|
||||
|
||||
for line in lines:
|
||||
trimmed = line.strip()
|
||||
|
||||
if not trimmed or is_line_comment(trimmed):
|
||||
continue
|
||||
elif trimmed.startswith("/*"):
|
||||
comment = True
|
||||
elif comment and trimmed.endswith("*/"):
|
||||
comment = False
|
||||
elif comment:
|
||||
continue
|
||||
elif trimmed:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def insert_toplevel_maybe_local_annotation(filename, lines):
|
||||
'''
|
||||
Find a suitable place to insert an overlay[local?] annotation at the top of the file.
|
||||
Returns a pair consisting of description and the modified lines or None if no overlay
|
||||
annotation is necessary (e.g., for files that only contain comments).
|
||||
'''
|
||||
if only_comments(lines):
|
||||
return None
|
||||
|
||||
i = find_file_level_module_declaration(lines)
|
||||
if not i == None:
|
||||
out_lines = lines[:i]
|
||||
out_lines.append("overlay[local?]\n")
|
||||
out_lines.extend(lines[i:])
|
||||
return (f"Annotating \"{filename}\" via existing file-level module statement", out_lines)
|
||||
|
||||
i = find_file_module_qldoc_declaration(lines)
|
||||
if not i == None:
|
||||
out_lines = lines[:i+1]
|
||||
out_lines.append("overlay[local?]\n")
|
||||
out_lines.append("module;\n")
|
||||
out_lines.extend(lines[i+1:])
|
||||
return (f"Annotating \"{filename}\" which has a file-level module qldoc", out_lines)
|
||||
|
||||
out_lines = ["overlay[local?]\n", "module;\n", "\n"] + lines
|
||||
return (f"Annotating \"{filename}\" without file-level module qldoc", out_lines)
|
||||
|
||||
|
||||
def insert_overlay_caller_annotations(lines):
|
||||
'''
|
||||
Mark pragma[inline] predicates as overlay[caller?] if they are not declared private.
|
||||
'''
|
||||
out_lines = []
|
||||
for i, line in enumerate(lines):
|
||||
trimmed = line.strip()
|
||||
if trimmed == "pragma[inline]":
|
||||
if i + 1 < len(lines) and not "private" in lines[i+1]:
|
||||
whitespace = line[0: line.find(trimmed)]
|
||||
out_lines.append(f"{whitespace}overlay[caller?]\n")
|
||||
out_lines.append(line)
|
||||
return out_lines
|
||||
|
||||
|
||||
def annotate_as_appropriate(filename, lines):
|
||||
'''
|
||||
Insert new overlay[...] annotations according to heuristics in files without existing
|
||||
overlay annotations.
|
||||
|
||||
Returns None if no annotations are needed. Otherwise, returns a pair consisting of a
|
||||
string describing the action taken and the modified content as a list of lines.
|
||||
'''
|
||||
if has_overlay_annotations(lines):
|
||||
return None
|
||||
|
||||
# These simple heuristics filter out those .qll files that we no _not_ want to annotate
|
||||
# as overlay[local?]. It is not clear that these heuristics are exactly what we want,
|
||||
# but they seem to work well enough for now (as determined by speed and accuracy numbers).
|
||||
if (filename.endswith("Test.qll") or
|
||||
((filename.endswith("Query.qll") or filename.endswith("Config.qll")) and
|
||||
any("implements DataFlow::ConfigSig" in line for line in lines))):
|
||||
return None
|
||||
elif not any(line for line in lines if line.strip()):
|
||||
return None
|
||||
|
||||
lines = insert_overlay_caller_annotations(lines)
|
||||
return insert_toplevel_maybe_local_annotation(filename, lines)
|
||||
|
||||
|
||||
def process_single_file(write, filename):
|
||||
'''
|
||||
Process a single file, annotating it as appropriate.
|
||||
If write is set, the changes are written back to the file.
|
||||
Returns True if the file requires changes.
|
||||
'''
|
||||
with open(filename) as f:
|
||||
old = [line for line in f]
|
||||
|
||||
annotate_result = annotate_as_appropriate(filename, old)
|
||||
if annotate_result is None:
|
||||
return False
|
||||
|
||||
if not write:
|
||||
return True
|
||||
|
||||
new = annotate_result[1]
|
||||
|
||||
diff = context_diff(old, new, fromfile=filename, tofile=filename)
|
||||
diff = [line for line in diff]
|
||||
if diff:
|
||||
print(annotate_result[0])
|
||||
for line in diff:
|
||||
print(line.rstrip())
|
||||
with open(filename, "w") as out_file:
|
||||
for line in new:
|
||||
out_file.write(line)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--check":
|
||||
check = True
|
||||
langs = sys.argv[2:]
|
||||
else:
|
||||
check = False
|
||||
langs = sys.argv[1:]
|
||||
|
||||
dirs = []
|
||||
for lang in langs:
|
||||
if lang in ["cpp", "go", "csharp", "java", "javascript", "python", "ruby", "rust", "swift"]:
|
||||
dirs.append(f"{lang}/ql/lib")
|
||||
else:
|
||||
raise Exception(f"Unknown language \"{lang}\".")
|
||||
|
||||
if dirs:
|
||||
dirs.append("shared")
|
||||
|
||||
missingAnnotations = []
|
||||
|
||||
for roots in dirs:
|
||||
for dirpath, dirnames, filenames in os.walk(roots):
|
||||
for filename in filenames:
|
||||
if filename.endswith(".qll") and not dirpath.endswith("tutorial"):
|
||||
path = os.path.join(dirpath, filename)
|
||||
res = process_single_file(not check, path)
|
||||
if check and res:
|
||||
missingAnnotations.append(path)
|
||||
|
||||
|
||||
if len(missingAnnotations) > 0:
|
||||
print("The following files have no overlay annotations:")
|
||||
for path in missingAnnotations[:10]:
|
||||
print("- " + path)
|
||||
if len(missingAnnotations) > 10:
|
||||
print("and " + str(len(missingAnnotations) - 10) + " additional files.")
|
||||
print()
|
||||
print("Please manually add overlay annotations or use the config/add-overlay-annotations.py script to automatically add sensible default overlay annotations.")
|
||||
exit(1)
|
||||
@@ -1,16 +1,19 @@
|
||||
{
|
||||
"files": [
|
||||
"cpp/ql/lib/semmlecode.cpp.dbscheme",
|
||||
"javascript/ql/lib/semmlecode.javascript.dbscheme",
|
||||
"python/ql/lib/semmlecode.python.dbscheme",
|
||||
"ruby/ql/lib/ruby.dbscheme",
|
||||
"ql/ql/src/ql.dbscheme"
|
||||
],
|
||||
"fragments": [
|
||||
"/*- Compilations -*/",
|
||||
"/*- External data -*/",
|
||||
"/*- Files and folders -*/",
|
||||
"/*- Diagnostic messages -*/",
|
||||
"/*- Diagnostic messages: severity -*/",
|
||||
"/*- Source location prefix -*/",
|
||||
"/*- Database metadata -*/",
|
||||
"/*- Lines of code -*/",
|
||||
"/*- Configuration files with key value pairs -*/",
|
||||
"/*- YAML -*/",
|
||||
@@ -20,6 +23,7 @@
|
||||
"/*- DEPRECATED: Snapshot date -*/",
|
||||
"/*- DEPRECATED: Duplicate code -*/",
|
||||
"/*- DEPRECATED: Version control data -*/",
|
||||
"/*- C++ dbscheme -*/",
|
||||
"/*- JavaScript-specific part -*/",
|
||||
"/*- Ruby dbscheme -*/",
|
||||
"/*- Erb dbscheme -*/",
|
||||
|
||||
@@ -8,9 +8,9 @@ needs_an_re = re.compile(r'^(?!Unary)[AEIOU]') # Name requiring "an" instead of
|
||||
start_qldoc_re = re.compile(r'^\s*/\*\*') # Start of a QLDoc comment
|
||||
end_qldoc_re = re.compile(r'\*/\s*$') # End of a QLDoc comment
|
||||
blank_qldoc_line_re = re.compile(r'^\s*\*\s*$') # A line in a QLDoc comment with only the '*'
|
||||
instruction_class_re = re.compile(r'^class (?P<name>[A-aa-z0-9]+)Instruction\s') # Declaration of an `Instruction` class
|
||||
opcode_base_class_re = re.compile(r'^abstract class (?P<name>[A-aa-z0-9]+)Opcode\s') # Declaration of an `Opcode` base class
|
||||
opcode_class_re = re.compile(r'^ class (?P<name>[A-aa-z0-9]+)\s') # Declaration of an `Opcode` class
|
||||
instruction_class_re = re.compile(r'^class (?P<name>[A-Za-z0-9]+)Instruction\s') # Declaration of an `Instruction` class
|
||||
opcode_base_class_re = re.compile(r'^abstract class (?P<name>[A-Za-z0-9]+)Opcode\s') # Declaration of an `Opcode` base class
|
||||
opcode_class_re = re.compile(r'^ class (?P<name>[A-Za-z0-9]+)\s') # Declaration of an `Opcode` class
|
||||
|
||||
script_dir = path.realpath(path.dirname(__file__))
|
||||
instruction_path = path.realpath(path.join(script_dir, '../cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll'))
|
||||
|
||||
2428
cpp/downgrades/5491582ac8511726e12fae3e2399000f9201cd9a/old.dbscheme
Normal file
2428
cpp/downgrades/5491582ac8511726e12fae3e2399000f9201cd9a/old.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Remove unused external_package tables from the dbscheme
|
||||
compatibility: full
|
||||
@@ -0,0 +1,14 @@
|
||||
class BuiltinType extends @builtintype {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from BuiltinType type, string name, int kind, int kind_new, int size, int sign, int alignment
|
||||
where
|
||||
builtintypes(type, name, kind, size, sign, alignment) and
|
||||
if
|
||||
type instanceof @complex_fp16 or
|
||||
type instanceof @complex_std_bfloat16 or
|
||||
type instanceof @complex_std_float16
|
||||
then kind_new = 2
|
||||
else kind_new = kind
|
||||
select type, name, kind_new, size, sign, alignment
|
||||
2509
cpp/downgrades/7bc12b02a4363149f0727a4bce07952dbb9d98aa/old.dbscheme
Normal file
2509
cpp/downgrades/7bc12b02a4363149f0727a4bce07952dbb9d98aa/old.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
description: Introduce new complex 16-bit floating-point types
|
||||
compatibility: backwards
|
||||
builtintypes.rel: run builtintypes.qlo
|
||||
@@ -0,0 +1,9 @@
|
||||
class Function extends @function {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from Function f, string n, int k, int new_k
|
||||
where
|
||||
functions(f, n, k) and
|
||||
if builtin_functions(f) then new_k = 6 else new_k = k
|
||||
select f, n, new_k
|
||||
2433
cpp/downgrades/801b2f03360d78c85f51fbad9b75956fa8d58b00/old.dbscheme
Normal file
2433
cpp/downgrades/801b2f03360d78c85f51fbad9b75956fa8d58b00/old.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
description: Move builtin function identification to its own table
|
||||
compatibility: full
|
||||
functions.rel: run functions.qlo
|
||||
builtin_functions.rel: delete
|
||||
2451
cpp/downgrades/827dbc206ea55377e032a8a934c8903fedc50fa0/old.dbscheme
Normal file
2451
cpp/downgrades/827dbc206ea55377e032a8a934c8903fedc50fa0/old.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: sync dbscheme and delete svn tables
|
||||
compatibility: full
|
||||
@@ -0,0 +1,161 @@
|
||||
class Accessible extends @accessible {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Container extends @container {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Expr extends @expr {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Initialiser extends @initialiser {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Location extends @location_default {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Stmt extends @stmt {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
predicate isLocationDefault(Location l) {
|
||||
diagnostics(_, _, _, _, _, l)
|
||||
or
|
||||
macroinvocations(_, _, l, _)
|
||||
or
|
||||
fun_decls(_, _, _, _, l)
|
||||
or
|
||||
var_decls(_, _, _, _, l)
|
||||
or
|
||||
type_decls(_, _, l)
|
||||
or
|
||||
namespace_decls(_, _, l, _)
|
||||
or
|
||||
namespace_decls(_, _, _, l)
|
||||
or
|
||||
usings(_, _, l, _)
|
||||
or
|
||||
static_asserts(_, _, _, l, _)
|
||||
or
|
||||
enumconstants(_, _, _, _, _, l)
|
||||
or
|
||||
concept_templates(_, _, l)
|
||||
or
|
||||
attributes(_, _, _, _, l)
|
||||
or
|
||||
attribute_args(_, _, _, _, l)
|
||||
or
|
||||
derivations(_, _, _, _, l)
|
||||
or
|
||||
frienddecls(_, _, _, l)
|
||||
or
|
||||
comments(_, _, l)
|
||||
or
|
||||
namequalifiers(_, _, _, l)
|
||||
or
|
||||
lambda_capture(_, _, _, _, _, _, l)
|
||||
or
|
||||
preprocdirects(_, _, l)
|
||||
or
|
||||
xmllocations(_, l)
|
||||
or
|
||||
locations_default(l, _, 0, 0, 0, 0) // For containers.
|
||||
}
|
||||
|
||||
predicate isLocationExpr(Location l) {
|
||||
initialisers(_, _, _, l)
|
||||
or
|
||||
exprs(_, _, l)
|
||||
}
|
||||
|
||||
predicate isLocationStmt(Location l) { stmts(_, _, l) }
|
||||
|
||||
newtype TExprOrStmtLocation =
|
||||
TExprLocation(Location l, Container c, int startLine, int startColumn, int endLine, int endColumn) {
|
||||
isLocationExpr(l) and
|
||||
(isLocationDefault(l) or isLocationStmt(l)) and
|
||||
locations_default(l, c, startLine, startColumn, endLine, endColumn)
|
||||
} or
|
||||
TStmtLocation(Location l, Container c, int startLine, int startColumn, int endLine, int endColumn) {
|
||||
isLocationStmt(l) and
|
||||
(isLocationDefault(l) or isLocationExpr(l)) and
|
||||
locations_default(l, c, startLine, startColumn, endLine, endColumn)
|
||||
}
|
||||
|
||||
module Fresh = QlBuiltins::NewEntity<TExprOrStmtLocation>;
|
||||
|
||||
class NewLocationBase = @location_default or Fresh::EntityId;
|
||||
|
||||
class NewLocation extends NewLocationBase {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
query predicate new_locations_default(
|
||||
NewLocation l, Container c, int startLine, int startColumn, int endLine, int endColumn
|
||||
) {
|
||||
isLocationDefault(l) and
|
||||
locations_default(l, c, startLine, startColumn, endLine, endColumn)
|
||||
}
|
||||
|
||||
query predicate new_locations_expr(
|
||||
NewLocation l, Container c, int startLine, int startColumn, int endLine, int endColumn
|
||||
) {
|
||||
exists(Location l_old |
|
||||
isLocationExpr(l_old) and
|
||||
locations_default(l_old, c, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
if not isLocationDefault(l_old) and not isLocationStmt(l)
|
||||
then l = l_old
|
||||
else l = Fresh::map(TExprLocation(l_old, c, startLine, startColumn, endLine, endColumn))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate new_locations_stmt(
|
||||
NewLocation l, Container c, int startLine, int startColumn, int endLine, int endColumn
|
||||
) {
|
||||
exists(Location l_old |
|
||||
isLocationStmt(l_old) and
|
||||
locations_default(l_old, c, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
if not isLocationDefault(l_old) and not isLocationExpr(l)
|
||||
then l = l_old
|
||||
else l = Fresh::map(TStmtLocation(l_old, c, startLine, startColumn, endLine, endColumn))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate new_exprs(Expr e, int kind, NewLocation l) {
|
||||
exists(Location l_old, Container c, int startLine, int startColumn, int endLine, int endColumn |
|
||||
exprs(e, kind, l_old) and
|
||||
locations_default(l_old, c, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
if not isLocationDefault(l_old) and not isLocationStmt(l)
|
||||
then l = l_old
|
||||
else l = Fresh::map(TExprLocation(l_old, c, startLine, startColumn, endLine, endColumn))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate new_initialisers(Initialiser i, Accessible v, Expr e, NewLocation l) {
|
||||
exists(Location l_old, Container c, int startLine, int startColumn, int endLine, int endColumn |
|
||||
initialisers(i, v, e, l_old) and
|
||||
locations_default(l_old, c, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
if not isLocationDefault(l_old) and not isLocationStmt(l)
|
||||
then l = l_old
|
||||
else l = Fresh::map(TExprLocation(l_old, c, startLine, startColumn, endLine, endColumn))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate new_stmts(Stmt s, int kind, NewLocation l) {
|
||||
exists(Location l_old, Container c, int startLine, int startColumn, int endLine, int endColumn |
|
||||
stmts(s, kind, l_old) and
|
||||
locations_default(l_old, c, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
if not isLocationDefault(l_old) and not isLocationExpr(l)
|
||||
then l = l_old
|
||||
else l = Fresh::map(TStmtLocation(l_old, c, startLine, startColumn, endLine, endColumn))
|
||||
)
|
||||
}
|
||||
2475
cpp/downgrades/e70d0b653187b93d9688f21c9db46bb1cd46ab78/old.dbscheme
Normal file
2475
cpp/downgrades/e70d0b653187b93d9688f21c9db46bb1cd46ab78/old.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
description: Merge location tables
|
||||
compatibility: partial
|
||||
locations_default.rel: run downgrades.ql new_locations_default
|
||||
locations_expr.rel: run downgrades.ql new_locations_expr
|
||||
locations_stmt.rel: run downgrades.ql new_locations_stmt
|
||||
exprs.rel: run downgrades.ql new_exprs
|
||||
initialisers.rel: run downgrades.ql new_initialisers
|
||||
stmts.rel: run downgrades.ql new_stmts
|
||||
@@ -1,3 +1,20 @@
|
||||
## 5.2.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `ThrowingFunction` class (`semmle.code.cpp.models.interfaces.Throwing`) has been deprecated. Please use the `AlwaysSehThrowingFunction` class instead.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added a predicate `getAnAttribute` to `Namespace` to retrieve a namespace attribute.
|
||||
* The Microsoft-specific `__leave` statement is now supported.
|
||||
* A new class `LeaveStmt` extending `JumpStmt` was added to represent `__leave` statements.
|
||||
* Added a predicate `hasParameterList` to `LambdaExpression` to capture whether a lambda has an explicitly specified parameter list.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `resolveTypedefs` now properly resolves typedefs for `ArrayType`s.
|
||||
|
||||
## 5.1.0
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `ThrowingFunction` class (`semmle.code.cpp.models.interfaces.Throwing`) has been deprecated. Please use the `AlwaysSehThrowingFunction` class instead.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a predicate `hasParameterList` to `LambdaExpression` to capture whether a lambda has an explicitly specified parameter list.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* The Microsoft-specific `__leave` statement is now supported.
|
||||
* A new class `LeaveStmt` extending `JumpStmt` was added to represent `__leave` statements.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added a predicate `getAnAttribute` to `Namespace` to retrieve a namespace attribute.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: fix
|
||||
---
|
||||
* `resolveTypedefs` now properly resolves typedefs for `ArrayType`s.
|
||||
4
cpp/ql/lib/change-notes/2025-06-20-oracle-oci-models.md
Normal file
4
cpp/ql/lib/change-notes/2025-06-20-oracle-oci-models.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added `sql-injection` sink models for the Oracle Call Interface (OCI) database library functions `OCIStmtPrepare` and `OCIStmtPrepare2`.
|
||||
4
cpp/ql/lib/change-notes/2025-06-24-float16 copy.md
Normal file
4
cpp/ql/lib/change-notes/2025-06-24-float16 copy.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The analysis of C/C++ code targeting 64-bit Arm platforms has been improved. This includes support for the Arm-specific builtin functions, support for the `arm_neon.h` header and Neon vector types, and support for the `fp8` scalar type. The `arm_sve.h` header and scalable vectors are only partially supported at this point.
|
||||
4
cpp/ql/lib/change-notes/2025-06-24-float16.md
Normal file
4
cpp/ql/lib/change-notes/2025-06-24-float16.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added support for `__fp16 _Complex` and `__bf16 _Complex` types
|
||||
4
cpp/ql/lib/change-notes/2025-06-27-locations.md
Normal file
4
cpp/ql/lib/change-notes/2025-06-27-locations.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `UnknownDefaultLocation`, `UnknownExprLocation`, and `UnknownStmtLocation` classes have been deprecated. Use `UnknownLocation` instead.
|
||||
16
cpp/ql/lib/change-notes/released/5.2.0.md
Normal file
16
cpp/ql/lib/change-notes/released/5.2.0.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## 5.2.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `ThrowingFunction` class (`semmle.code.cpp.models.interfaces.Throwing`) has been deprecated. Please use the `AlwaysSehThrowingFunction` class instead.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added a predicate `getAnAttribute` to `Namespace` to retrieve a namespace attribute.
|
||||
* The Microsoft-specific `__leave` statement is now supported.
|
||||
* A new class `LeaveStmt` extending `JumpStmt` was added to represent `__leave` statements.
|
||||
* Added a predicate `hasParameterList` to `LambdaExpression` to capture whether a lambda has an explicitly specified parameter list.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* `resolveTypedefs` now properly resolves typedefs for `ArrayType`s.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 5.1.0
|
||||
lastReleaseVersion: 5.2.0
|
||||
|
||||
@@ -8,7 +8,7 @@ module CryptoInput implements InputSig<Language::Location> {
|
||||
|
||||
class LocatableElement = Language::Locatable;
|
||||
|
||||
class UnknownLocation = Language::UnknownDefaultLocation;
|
||||
class UnknownLocation = Language::UnknownLocation;
|
||||
|
||||
LocatableElement dfn_to_element(DataFlow::Node node) {
|
||||
result = node.asExpr() or
|
||||
@@ -56,7 +56,7 @@ module ArtifactFlowConfig implements DataFlow::ConfigSig {
|
||||
module ArtifactFlow = DataFlow::Global<ArtifactFlowConfig>;
|
||||
|
||||
/**
|
||||
* Artifact output to node input configuration
|
||||
* An artifact output to node input configuration
|
||||
*/
|
||||
abstract class AdditionalFlowInputStep extends DataFlow::Node {
|
||||
abstract DataFlow::Node getOutput();
|
||||
@@ -91,9 +91,8 @@ module GenericDataSourceFlowConfig implements DataFlow::ConfigSig {
|
||||
|
||||
module GenericDataSourceFlow = TaintTracking::Global<GenericDataSourceFlowConfig>;
|
||||
|
||||
private class ConstantDataSource extends Crypto::GenericConstantSourceInstance instanceof Literal {
|
||||
ConstantDataSource() { this instanceof OpenSslGenericSourceCandidateLiteral }
|
||||
|
||||
private class ConstantDataSource extends Crypto::GenericConstantSourceInstance instanceof OpenSslGenericSourceCandidateLiteral
|
||||
{
|
||||
override DataFlow::Node getOutputNode() { result.asExpr() = this }
|
||||
|
||||
override predicate flowsTo(Crypto::FlowAwareElement other) {
|
||||
|
||||
@@ -48,7 +48,7 @@ module KnownOpenSslAlgorithmToAlgorithmValueConsumerConfig implements DataFlow::
|
||||
module KnownOpenSslAlgorithmToAlgorithmValueConsumerFlow =
|
||||
DataFlow::Global<KnownOpenSslAlgorithmToAlgorithmValueConsumerConfig>;
|
||||
|
||||
module RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig implements DataFlow::ConfigSig {
|
||||
module RsaPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof OpenSslPaddingLiteral }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
@@ -60,8 +60,8 @@ module RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig implements DataF
|
||||
}
|
||||
}
|
||||
|
||||
module RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow =
|
||||
DataFlow::Global<RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig>;
|
||||
module RsaPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow =
|
||||
DataFlow::Global<RsaPaddingAlgorithmToPaddingAlgorithmValueConsumerConfig>;
|
||||
|
||||
class OpenSslAlgorithmAdditionalFlowStep extends AdditionalFlowInputStep {
|
||||
OpenSslAlgorithmAdditionalFlowStep() { exists(AlgorithmPassthroughCall c | c.getInNode() = this) }
|
||||
@@ -114,11 +114,11 @@ class CopyAndDupAlgorithmPassthroughCall extends AlgorithmPassthroughCall {
|
||||
override DataFlow::Node getOutNode() { result = outNode }
|
||||
}
|
||||
|
||||
class NIDToPointerPassthroughCall extends AlgorithmPassthroughCall {
|
||||
class NidToPointerPassthroughCall extends AlgorithmPassthroughCall {
|
||||
DataFlow::Node inNode;
|
||||
DataFlow::Node outNode;
|
||||
|
||||
NIDToPointerPassthroughCall() {
|
||||
NidToPointerPassthroughCall() {
|
||||
this.getTarget().getName() in ["OBJ_nid2obj", "OBJ_nid2ln", "OBJ_nid2sn"] and
|
||||
inNode.asExpr() = this.getArgument(0) and
|
||||
outNode.asExpr() = this
|
||||
@@ -150,11 +150,11 @@ class PointerToPointerPassthroughCall extends AlgorithmPassthroughCall {
|
||||
override DataFlow::Node getOutNode() { result = outNode }
|
||||
}
|
||||
|
||||
class PointerToNIDPassthroughCall extends AlgorithmPassthroughCall {
|
||||
class PointerToNidPassthroughCall extends AlgorithmPassthroughCall {
|
||||
DataFlow::Node inNode;
|
||||
DataFlow::Node outNode;
|
||||
|
||||
PointerToNIDPassthroughCall() {
|
||||
PointerToNidPassthroughCall() {
|
||||
this.getTarget().getName() in ["OBJ_obj2nid", "OBJ_ln2nid", "OBJ_sn2nid", "OBJ_txt2nid"] and
|
||||
(
|
||||
inNode.asIndirectExpr() = this.getArgument(0)
|
||||
|
||||
@@ -5,36 +5,35 @@ private import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmCon
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.DirectAlgorithmValueConsumer
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
|
||||
private import AlgToAVCFlow
|
||||
private import codeql.quantum.experimental.Standardization::Types::KeyOpAlg as KeyOpAlg
|
||||
|
||||
/**
|
||||
* Given a `KnownOpenSslBlockModeAlgorithmExpr`, converts this to a block family type.
|
||||
* Does not bind if there is no mapping (no mapping to 'unknown' or 'other').
|
||||
*/
|
||||
predicate knownOpenSslConstantToBlockModeFamilyType(
|
||||
KnownOpenSslBlockModeAlgorithmExpr e, Crypto::TBlockCipherModeOfOperationType type
|
||||
KnownOpenSslBlockModeAlgorithmExpr e, KeyOpAlg::ModeOfOperationType type
|
||||
) {
|
||||
exists(string name |
|
||||
name = e.(KnownOpenSslAlgorithmExpr).getNormalizedName() and
|
||||
(
|
||||
name.matches("CBC") and type instanceof Crypto::CBC
|
||||
name = "CBC" and type instanceof KeyOpAlg::CBC
|
||||
or
|
||||
name.matches("CFB%") and type instanceof Crypto::CFB
|
||||
name = "CFB%" and type instanceof KeyOpAlg::CFB
|
||||
or
|
||||
name.matches("CTR") and type instanceof Crypto::CTR
|
||||
name = "CTR" and type instanceof KeyOpAlg::CTR
|
||||
or
|
||||
name.matches("GCM") and type instanceof Crypto::GCM
|
||||
name = "GCM" and type instanceof KeyOpAlg::GCM
|
||||
or
|
||||
name.matches("OFB") and type instanceof Crypto::OFB
|
||||
name = "OFB" and type instanceof KeyOpAlg::OFB
|
||||
or
|
||||
name.matches("XTS") and type instanceof Crypto::XTS
|
||||
name = "XTS" and type instanceof KeyOpAlg::XTS
|
||||
or
|
||||
name.matches("CCM") and type instanceof Crypto::CCM
|
||||
name = "CCM" and type instanceof KeyOpAlg::CCM
|
||||
or
|
||||
name.matches("GCM") and type instanceof Crypto::GCM
|
||||
name = "CCM" and type instanceof KeyOpAlg::CCM
|
||||
or
|
||||
name.matches("CCM") and type instanceof Crypto::CCM
|
||||
or
|
||||
name.matches("ECB") and type instanceof Crypto::ECB
|
||||
name = "ECB" and type instanceof KeyOpAlg::ECB
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -64,10 +63,10 @@ class KnownOpenSslBlockModeConstantAlgorithmInstance extends OpenSslAlgorithmIns
|
||||
getterCall = this
|
||||
}
|
||||
|
||||
override Crypto::TBlockCipherModeOfOperationType getModeType() {
|
||||
override KeyOpAlg::ModeOfOperationType getModeType() {
|
||||
knownOpenSslConstantToBlockModeFamilyType(this, result)
|
||||
or
|
||||
not knownOpenSslConstantToBlockModeFamilyType(this, _) and result = Crypto::OtherMode()
|
||||
not knownOpenSslConstantToBlockModeFamilyType(this, _) and result = KeyOpAlg::OtherMode()
|
||||
}
|
||||
|
||||
// NOTE: I'm not going to attempt to parse out the mode specific part, so returning
|
||||
|
||||
@@ -33,9 +33,9 @@ predicate knownOpenSslConstantToCipherFamilyType(
|
||||
or
|
||||
name.matches("CAST5%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CAST5())
|
||||
or
|
||||
name.matches("2DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DoubleDES())
|
||||
name.matches("2DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DOUBLE_DES())
|
||||
or
|
||||
name.matches("3DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::TripleDES())
|
||||
name.matches("3DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::TRIPLE_DES())
|
||||
or
|
||||
name.matches("DES%") and type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DES())
|
||||
or
|
||||
@@ -113,7 +113,7 @@ class KnownOpenSslCipherConstantAlgorithmInstance extends OpenSslAlgorithmInstan
|
||||
this.(KnownOpenSslCipherAlgorithmExpr).getExplicitKeySize() = result
|
||||
}
|
||||
|
||||
override Crypto::KeyOpAlg::Algorithm getAlgorithmType() {
|
||||
override KeyOpAlg::AlgorithmType getAlgorithmType() {
|
||||
knownOpenSslConstantToCipherFamilyType(this, result)
|
||||
or
|
||||
not knownOpenSslConstantToCipherFamilyType(this, _) and
|
||||
|
||||
@@ -39,8 +39,14 @@ class KnownOpenSslEllipticCurveConstantAlgorithmInstance extends OpenSslAlgorith
|
||||
result = this.(Call).getTarget().getName()
|
||||
}
|
||||
|
||||
override Crypto::TEllipticCurveType getEllipticCurveType() {
|
||||
Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getParsedEllipticCurveName(), _, result)
|
||||
override Crypto::EllipticCurveFamilyType getEllipticCurveFamilyType() {
|
||||
if
|
||||
Crypto::ellipticCurveNameToKnownKeySizeAndFamilyMapping(this.getParsedEllipticCurveName(), _,
|
||||
_)
|
||||
then
|
||||
Crypto::ellipticCurveNameToKnownKeySizeAndFamilyMapping(this.getParsedEllipticCurveName(), _,
|
||||
result)
|
||||
else result = Crypto::OtherEllipticCurveType()
|
||||
}
|
||||
|
||||
override string getParsedEllipticCurveName() {
|
||||
@@ -48,7 +54,7 @@ class KnownOpenSslEllipticCurveConstantAlgorithmInstance extends OpenSslAlgorith
|
||||
}
|
||||
|
||||
override int getKeySize() {
|
||||
Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.(KnownOpenSslAlgorithmExpr)
|
||||
Crypto::ellipticCurveNameToKnownKeySizeAndFamilyMapping(this.(KnownOpenSslAlgorithmExpr)
|
||||
.getNormalizedName(), result, _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,21 @@ predicate knownOpenSslConstantToHashFamilyType(
|
||||
exists(string name |
|
||||
name = e.(KnownOpenSslAlgorithmExpr).getNormalizedName() and
|
||||
(
|
||||
name.matches("BLAKE2B") and type instanceof Crypto::BLAKE2B
|
||||
name = "BLAKE2B" and type instanceof Crypto::BLAKE2B
|
||||
or
|
||||
name.matches("BLAKE2S") and type instanceof Crypto::BLAKE2S
|
||||
name = "BLAKE2S" and type instanceof Crypto::BLAKE2S
|
||||
or
|
||||
name.matches("GOST%") and type instanceof Crypto::GOSTHash
|
||||
name.matches("GOST%") and type instanceof Crypto::GOST_HASH
|
||||
or
|
||||
name.matches("MD2") and type instanceof Crypto::MD2
|
||||
name = "MD2" and type instanceof Crypto::MD2
|
||||
or
|
||||
name.matches("MD4") and type instanceof Crypto::MD4
|
||||
name = "MD4" and type instanceof Crypto::MD4
|
||||
or
|
||||
name.matches("MD5") and type instanceof Crypto::MD5
|
||||
name = "MD5" and type instanceof Crypto::MD5
|
||||
or
|
||||
name.matches("MDC2") and type instanceof Crypto::MDC2
|
||||
name = "MDC2" and type instanceof Crypto::MDC2
|
||||
or
|
||||
name.matches("POLY1305") and type instanceof Crypto::POLY1305
|
||||
name = "POLY1305" and type instanceof Crypto::POLY1305
|
||||
or
|
||||
name.matches(["SHA", "SHA1"]) and type instanceof Crypto::SHA1
|
||||
or
|
||||
@@ -33,13 +33,13 @@ predicate knownOpenSslConstantToHashFamilyType(
|
||||
or
|
||||
name.matches("SHA3-%") and type instanceof Crypto::SHA3
|
||||
or
|
||||
name.matches(["SHAKE"]) and type instanceof Crypto::SHAKE
|
||||
name = "SHAKE" and type instanceof Crypto::SHAKE
|
||||
or
|
||||
name.matches("SM3") and type instanceof Crypto::SM3
|
||||
name = "SM3" and type instanceof Crypto::SM3
|
||||
or
|
||||
name.matches("RIPEMD160") and type instanceof Crypto::RIPEMD160
|
||||
name = "RIPEMD160" and type instanceof Crypto::RIPEMD160
|
||||
or
|
||||
name.matches("WHIRLPOOL") and type instanceof Crypto::WHIRLPOOL
|
||||
name = "WHIRLPOOL" and type instanceof Crypto::WHIRLPOOL
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -210,7 +210,8 @@ string getAlgorithmAlias(string alias) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds aliases of known alagorithms defined by users (through obj_name_add and various macros pointing to this function)
|
||||
* Holds for aliases of known algorithms defined by users
|
||||
* (through obj_name_add and various macros pointing to this function).
|
||||
*
|
||||
* The `target` and `alias` are converted to lowercase to be of a standard form.
|
||||
*/
|
||||
@@ -222,7 +223,7 @@ predicate customAliases(string target, string alias) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A hard-coded mapping of known algorithm aliases in OpenSsl.
|
||||
* Holds for a hard-coded mapping of known algorithm aliases in OpenSsl.
|
||||
* This was derived by applying the same kind of logic foun din `customAliases` to the
|
||||
* OpenSsl code base directly.
|
||||
*
|
||||
|
||||
@@ -7,7 +7,7 @@ private import experimental.quantum.OpenSSL.Operations.OpenSSLOperations
|
||||
private import AlgToAVCFlow
|
||||
|
||||
class KnownOpenSslMacConstantAlgorithmInstance extends OpenSslAlgorithmInstance,
|
||||
Crypto::MACAlgorithmInstance instanceof KnownOpenSslMacAlgorithmExpr
|
||||
Crypto::MacAlgorithmInstance instanceof KnownOpenSslMacAlgorithmExpr
|
||||
{
|
||||
OpenSslAlgorithmValueConsumer getterCall;
|
||||
|
||||
@@ -39,14 +39,14 @@ class KnownOpenSslMacConstantAlgorithmInstance extends OpenSslAlgorithmInstance,
|
||||
result = this.(Call).getTarget().getName()
|
||||
}
|
||||
|
||||
override Crypto::TMACType getMacType() {
|
||||
this instanceof KnownOpenSslHMacAlgorithmExpr and result instanceof Crypto::THMAC
|
||||
override Crypto::MacType getMacType() {
|
||||
this instanceof KnownOpenSslHMacAlgorithmExpr and result = Crypto::HMAC()
|
||||
or
|
||||
this instanceof KnownOpenSslCMacAlgorithmExpr and result instanceof Crypto::TCMAC
|
||||
this instanceof KnownOpenSslCMacAlgorithmExpr and result = Crypto::CMAC()
|
||||
}
|
||||
}
|
||||
|
||||
class KnownOpenSslHMacConstantAlgorithmInstance extends Crypto::HMACAlgorithmInstance,
|
||||
class KnownOpenSslHMacConstantAlgorithmInstance extends Crypto::HmacAlgorithmInstance,
|
||||
KnownOpenSslMacConstantAlgorithmInstance
|
||||
{
|
||||
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() {
|
||||
@@ -54,13 +54,15 @@ class KnownOpenSslHMacConstantAlgorithmInstance extends Crypto::HMACAlgorithmIns
|
||||
then
|
||||
// ASSUMPTION: if there is an explicit hash algorithm, it is already modeled
|
||||
// and we can simply grab that model's AVC
|
||||
exists(OpenSslAlgorithmInstance inst | inst.getAvc() = result and inst = this)
|
||||
this.(OpenSslAlgorithmInstance).getAvc() = result
|
||||
else
|
||||
// ASSUMPTION: If no explicit algorithm is given, then it is assumed to be configured by
|
||||
// a signature operation
|
||||
exists(Crypto::SignatureOperationInstance s |
|
||||
s.getHashAlgorithmValueConsumer() = result and
|
||||
s.getAnAlgorithmValueConsumer() = this.getAvc()
|
||||
// ASSUMPTION: If no explicit algorithm is given, then find
|
||||
// where the current AVC traces to a HashAlgorithmIO consuming operation step.
|
||||
// TODO: need to consider getting reset values, tracing down to the first set for now
|
||||
exists(OperationStep s, AvcContextCreationStep avc |
|
||||
avc = this.getAvc() and
|
||||
avc.flowsToOperationStep(s) and
|
||||
s.getAlgorithmValueConsumerForInput(HashAlgorithmIO()) = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ private import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmCon
|
||||
private import AlgToAVCFlow
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.DirectAlgorithmValueConsumer
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
|
||||
private import codeql.quantum.experimental.Standardization::Types::KeyOpAlg as KeyOpAlg
|
||||
|
||||
/**
|
||||
* A class to define padding specific integer values.
|
||||
@@ -28,18 +29,18 @@ class OpenSslPaddingLiteral extends Literal {
|
||||
* Does not bind if there is no mapping (no mapping to 'unknown' or 'other').
|
||||
*/
|
||||
predicate knownOpenSslConstantToPaddingFamilyType(
|
||||
KnownOpenSslPaddingAlgorithmExpr e, Crypto::TPaddingType type
|
||||
KnownOpenSslPaddingAlgorithmExpr e, KeyOpAlg::PaddingSchemeType type
|
||||
) {
|
||||
exists(string name |
|
||||
name = e.(KnownOpenSslAlgorithmExpr).getNormalizedName() and
|
||||
(
|
||||
name.matches("OAEP") and type = Crypto::OAEP()
|
||||
name = "OAEP" and type = KeyOpAlg::OAEP()
|
||||
or
|
||||
name.matches("PSS") and type = Crypto::PSS()
|
||||
name = "PSS" and type = KeyOpAlg::PSS()
|
||||
or
|
||||
name.matches("PKCS7") and type = Crypto::PKCS7()
|
||||
name = "PKCS7" and type = KeyOpAlg::PKCS7()
|
||||
or
|
||||
name.matches("PKCS1V15") and type = Crypto::PKCS1_v1_5()
|
||||
name = "PKCS1V15" and type = KeyOpAlg::PKCS1_V1_5()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -85,7 +86,7 @@ class KnownOpenSslPaddingConstantAlgorithmInstance extends OpenSslAlgorithmInsta
|
||||
// Source is `this`
|
||||
src.asExpr() = this and
|
||||
// This traces to a padding-specific consumer
|
||||
RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow::flow(src, sink)
|
||||
RsaPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow::flow(src, sink)
|
||||
) and
|
||||
isPaddingSpecificConsumer = true
|
||||
}
|
||||
@@ -98,24 +99,24 @@ class KnownOpenSslPaddingConstantAlgorithmInstance extends OpenSslAlgorithmInsta
|
||||
|
||||
override OpenSslAlgorithmValueConsumer getAvc() { result = getterCall }
|
||||
|
||||
Crypto::TPaddingType getKnownPaddingType() {
|
||||
this.(Literal).getValue().toInt() in [1, 7, 8] and result = Crypto::PKCS1_v1_5()
|
||||
KeyOpAlg::PaddingSchemeType getKnownPaddingType() {
|
||||
this.(Literal).getValue().toInt() in [1, 7, 8] and result = KeyOpAlg::PKCS1_V1_5()
|
||||
or
|
||||
this.(Literal).getValue().toInt() = 3 and result = Crypto::NoPadding()
|
||||
this.(Literal).getValue().toInt() = 3 and result = KeyOpAlg::NoPadding()
|
||||
or
|
||||
this.(Literal).getValue().toInt() = 4 and result = Crypto::OAEP()
|
||||
this.(Literal).getValue().toInt() = 4 and result = KeyOpAlg::OAEP()
|
||||
or
|
||||
this.(Literal).getValue().toInt() = 5 and result = Crypto::ANSI_X9_23()
|
||||
this.(Literal).getValue().toInt() = 5 and result = KeyOpAlg::ANSI_X9_23()
|
||||
or
|
||||
this.(Literal).getValue().toInt() = 6 and result = Crypto::PSS()
|
||||
this.(Literal).getValue().toInt() = 6 and result = KeyOpAlg::PSS()
|
||||
}
|
||||
|
||||
override Crypto::TPaddingType getPaddingType() {
|
||||
override KeyOpAlg::PaddingSchemeType getPaddingType() {
|
||||
isPaddingSpecificConsumer = true and
|
||||
(
|
||||
result = this.getKnownPaddingType()
|
||||
or
|
||||
not exists(this.getKnownPaddingType()) and result = Crypto::OtherPadding()
|
||||
not exists(this.getKnownPaddingType()) and result = KeyOpAlg::OtherPadding()
|
||||
)
|
||||
or
|
||||
isPaddingSpecificConsumer = false and
|
||||
@@ -143,7 +144,7 @@ class KnownOpenSslPaddingConstantAlgorithmInstance extends OpenSslAlgorithmInsta
|
||||
// this instanceof Literal and
|
||||
// this.getValue().toInt() in [0, 1, 3, 4, 5, 6, 7, 8]
|
||||
// // TODO: trace to padding-specific consumers
|
||||
// RSAPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow
|
||||
// RsaPaddingAlgorithmToPaddingAlgorithmValueConsumerFlow
|
||||
// }
|
||||
// override string getRawPaddingAlgorithmName() { result = this.(Literal).getValue().toString() }
|
||||
// override Crypto::TPaddingType getPaddingType() {
|
||||
@@ -161,18 +162,18 @@ class KnownOpenSslPaddingConstantAlgorithmInstance extends OpenSslAlgorithmInsta
|
||||
// else result = Crypto::OtherPadding()
|
||||
// }
|
||||
// }
|
||||
class OAEPPaddingAlgorithmInstance extends Crypto::OAEPPaddingAlgorithmInstance,
|
||||
class OaepPaddingAlgorithmInstance extends Crypto::OaepPaddingAlgorithmInstance,
|
||||
KnownOpenSslPaddingConstantAlgorithmInstance
|
||||
{
|
||||
OAEPPaddingAlgorithmInstance() {
|
||||
this.(Crypto::PaddingAlgorithmInstance).getPaddingType() = Crypto::OAEP()
|
||||
OaepPaddingAlgorithmInstance() {
|
||||
this.(Crypto::PaddingAlgorithmInstance).getPaddingType() = KeyOpAlg::OAEP()
|
||||
}
|
||||
|
||||
override Crypto::HashAlgorithmInstance getOAEPEncodingHashAlgorithm() {
|
||||
override Crypto::HashAlgorithmInstance getOaepEncodingHashAlgorithm() {
|
||||
none() //TODO
|
||||
}
|
||||
|
||||
override Crypto::HashAlgorithmInstance getMGF1HashAlgorithm() {
|
||||
override Crypto::HashAlgorithmInstance getMgf1HashAlgorithm() {
|
||||
none() //TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class KnownOpenSslSignatureConstantAlgorithmInstance extends OpenSslAlgorithmIns
|
||||
none()
|
||||
}
|
||||
|
||||
override KeyOpAlg::Algorithm getAlgorithmType() {
|
||||
override KeyOpAlg::AlgorithmType getAlgorithmType() {
|
||||
knownOpenSslConstantToSignatureFamilyType(this, result)
|
||||
or
|
||||
not knownOpenSslConstantToSignatureFamilyType(this, _) and
|
||||
|
||||
@@ -4,10 +4,10 @@ private import experimental.quantum.OpenSSL.AlgorithmInstances.KnownAlgorithmCon
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumerBase
|
||||
|
||||
/**
|
||||
* Cases like EVP_MD5(),
|
||||
* there is no input, rather it directly gets an algorithm
|
||||
* and returns it.
|
||||
* Also includes operations directly using an algorithm
|
||||
* A call that is considered to inherently 'consume' an algorithm value.
|
||||
* E.g., cases like EVP_MD5(),
|
||||
* where there is no input, rather it directly gets an algorithm
|
||||
* and returns it. Also includes operations directly using an algorithm
|
||||
* like AES_encrypt().
|
||||
*/
|
||||
class DirectAlgorithmValueConsumer extends OpenSslAlgorithmValueConsumer instanceof OpenSslAlgorithmCall
|
||||
|
||||
@@ -7,7 +7,7 @@ private import experimental.quantum.OpenSSL.AlgorithmInstances.OpenSSLAlgorithmI
|
||||
abstract class HashAlgorithmValueConsumer extends OpenSslAlgorithmValueConsumer { }
|
||||
|
||||
/**
|
||||
* EVP_Q_Digest directly consumes algorithm constant values
|
||||
* An EVP_Q_Digest directly consumes algorithm constant values
|
||||
*/
|
||||
class Evp_Q_Digest_Algorithm_Consumer extends HashAlgorithmValueConsumer {
|
||||
Evp_Q_Digest_Algorithm_Consumer() { this.(Call).getTarget().getName() = "EVP_Q_digest" }
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
//TODO: model as data on open APIs should be able to get common flows, and obviate some of this
|
||||
// e.g., copy/dup calls, need to ingest those models for openSSL and refactor.
|
||||
/**
|
||||
* In OpenSSL, flow between 'context' parameters is often used to
|
||||
* store state/config of how an operation will eventually be performed.
|
||||
* Tracing algorithms and configurations to operations therefore
|
||||
* requires tracing context parameters for many OpenSSL apis.
|
||||
*
|
||||
* This library provides a dataflow analysis to track context parameters
|
||||
* between any two functions accepting openssl context parameters.
|
||||
* The dataflow takes into consideration flowing through duplication and copy calls
|
||||
* as well as flow through flow killers (free/reset calls).
|
||||
*
|
||||
* TODO: we may need to revisit 'free' as a dataflow killer, depending on how
|
||||
* we want to model use after frees.
|
||||
*
|
||||
* This library also provides classes to represent context Types and relevant
|
||||
* arguments/expressions.
|
||||
*/
|
||||
|
||||
import semmle.code.cpp.dataflow.new.DataFlow
|
||||
|
||||
/**
|
||||
* An openSSL CTX type, which is type for which the stripped underlying type
|
||||
* matches the pattern 'evp_%ctx_%st'.
|
||||
* This includes types like:
|
||||
* - EVP_CIPHER_CTX
|
||||
* - EVP_MD_CTX
|
||||
* - EVP_PKEY_CTX
|
||||
*/
|
||||
class CtxType extends Type {
|
||||
CtxType() {
|
||||
// It is possible for users to use the underlying type of the CTX variables
|
||||
// these have a name matching 'evp_%ctx_%st
|
||||
this.getUnspecifiedType().stripType().getName().matches("evp_%ctx_%st")
|
||||
or
|
||||
// In principal the above check should be sufficient, but in case of build mode none issues
|
||||
// i.e., if a typedef cannot be resolved,
|
||||
// or issues with properly stubbing test cases, we also explicitly check for the wrapping type defs
|
||||
// i.e., patterns matching 'EVP_%_CTX'
|
||||
exists(Type base | base = this or base = this.(DerivedType).getBaseType() |
|
||||
base.getName().matches("EVP_%_CTX")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pointer to a CtxType
|
||||
*/
|
||||
class CtxPointerExpr extends Expr {
|
||||
CtxPointerExpr() {
|
||||
this.getType() instanceof CtxType and
|
||||
this.getType() instanceof PointerType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call argument of type CtxPointerExpr.
|
||||
*/
|
||||
class CtxPointerArgument extends CtxPointerExpr {
|
||||
CtxPointerArgument() { exists(Call c | c.getAnArgument() = this) }
|
||||
|
||||
Call getCall() { result.getAnArgument() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call returning a CtxPointerExpr.
|
||||
*/
|
||||
private class CtxPointerReturn extends CtxPointerExpr instanceof Call {
|
||||
Call getCall() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call whose target contains 'free' or 'reset' and has an argument of type
|
||||
* CtxPointerArgument.
|
||||
*/
|
||||
private class CtxClearCall extends Call {
|
||||
CtxClearCall() {
|
||||
this.getTarget().getName().toLowerCase().matches(["%free%", "%reset%"]) and
|
||||
this.getAnArgument() instanceof CtxPointerArgument
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class CtxPassThroughCall extends Call {
|
||||
abstract DataFlow::Node getNode1();
|
||||
|
||||
abstract DataFlow::Node getNode2();
|
||||
}
|
||||
|
||||
/**
|
||||
* A call whose target contains 'copy' and has an argument of type
|
||||
* CtxPointerArgument.
|
||||
*/
|
||||
private class CtxCopyOutArgCall extends CtxPassThroughCall {
|
||||
DataFlow::Node n1;
|
||||
DataFlow::Node n2;
|
||||
|
||||
CtxCopyOutArgCall() {
|
||||
this.getTarget().getName().toLowerCase().matches("%copy%") and
|
||||
n1.asExpr() = this.getAnArgument() and
|
||||
n1.getType() instanceof CtxType and
|
||||
n2.asDefiningArgument() = this.getAnArgument() and
|
||||
n2.getType() instanceof CtxType and
|
||||
n1.asDefiningArgument() != n2.asExpr()
|
||||
}
|
||||
|
||||
override DataFlow::Node getNode1() { result = n1 }
|
||||
|
||||
override DataFlow::Node getNode2() { result = n2 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call whose target contains 'dup' and has an argument of type
|
||||
* CtxPointerArgument.
|
||||
*/
|
||||
private class CtxCopyReturnCall extends CtxPassThroughCall, CtxPointerExpr {
|
||||
DataFlow::Node n1;
|
||||
|
||||
CtxCopyReturnCall() {
|
||||
this.getTarget().getName().toLowerCase().matches("%dup%") and
|
||||
n1.asExpr() = this.getAnArgument() and
|
||||
n1.getType() instanceof CtxType
|
||||
}
|
||||
|
||||
override DataFlow::Node getNode1() { result = n1 }
|
||||
|
||||
override DataFlow::Node getNode2() { result.asExpr() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `EVP_PKEY_paramgen` acts as a kind of pass through.
|
||||
* It's output pkey is eventually used in a new operation generating
|
||||
* a fresh context pointer (e.g., `EVP_PKEY_CTX_new`).
|
||||
* It is easier to model this as a pass through
|
||||
* than to model the flow from the paramgen to the new key generation.
|
||||
*/
|
||||
private class CtxParamGenCall extends CtxPassThroughCall {
|
||||
DataFlow::Node n1;
|
||||
DataFlow::Node n2;
|
||||
|
||||
CtxParamGenCall() {
|
||||
this.getTarget().getName() = "EVP_PKEY_paramgen" and
|
||||
n1.asExpr() = this.getArgument(0) and
|
||||
(
|
||||
n2.asExpr() = this.getArgument(1)
|
||||
or
|
||||
n2.asDefiningArgument() = this.getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNode1() { result = n1 }
|
||||
|
||||
override DataFlow::Node getNode2() { result = n2 }
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current node gets is an argument to a function
|
||||
* that returns a pointer type, immediately flow through.
|
||||
* NOTE: this passthrough is required if we allow
|
||||
* intermediate steps to go into variables that are not a CTX type.
|
||||
* See for example `CtxParamGenCall`.
|
||||
*/
|
||||
private class CallArgToCtxRet extends CtxPassThroughCall, CtxPointerExpr {
|
||||
DataFlow::Node n1;
|
||||
DataFlow::Node n2;
|
||||
|
||||
CallArgToCtxRet() {
|
||||
this.getAnArgument() = n1.asExpr() and
|
||||
n2.asExpr() = this
|
||||
}
|
||||
|
||||
override DataFlow::Node getNode1() { result = n1 }
|
||||
|
||||
override DataFlow::Node getNode2() { result = n2 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A source Ctx of interest is any argument or return of type CtxPointerExpr.
|
||||
*/
|
||||
class CtxPointerSource extends CtxPointerExpr {
|
||||
CtxPointerSource() {
|
||||
this instanceof CtxPointerReturn or
|
||||
this instanceof CtxPointerArgument
|
||||
}
|
||||
|
||||
DataFlow::Node asNode() {
|
||||
result.asExpr() = this
|
||||
or
|
||||
result.asDefiningArgument() = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow from any CtxPointerSource to other CtxPointerSource.
|
||||
*/
|
||||
module OpenSslCtxSourceToSourceFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { exists(CtxPointerSource s | s.asNode() = source) }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { exists(CtxPointerSource s | s.asNode() = sink) }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
exists(CtxClearCall c | c.getAnArgument() = node.asExpr())
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(CtxPassThroughCall c | c.getNode1() = node1 and c.getNode2() = node2)
|
||||
}
|
||||
}
|
||||
|
||||
module OpenSslCtxSourceToArgumentFlow = DataFlow::Global<OpenSslCtxSourceToSourceFlowConfig>;
|
||||
|
||||
/**
|
||||
* Holds if there is a context flow from the source to the sink.
|
||||
*/
|
||||
predicate ctxSrcToSrcFlow(CtxPointerSource source, CtxPointerSource sink) {
|
||||
exists(DataFlow::Node a, DataFlow::Node b |
|
||||
OpenSslCtxSourceToArgumentFlow::flow(a, b) and
|
||||
a = source.asNode() and
|
||||
b = sink.asNode()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
import experimental.quantum.Language
|
||||
private import OpenSSLOperationBase
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||
import EVPPKeyCtxInitializer
|
||||
|
||||
/**
|
||||
* A base class for all EVP cipher operations.
|
||||
*/
|
||||
abstract class EvpCipherInitializer extends OperationStep {
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and
|
||||
type = PrimaryAlgorithmIO() and
|
||||
// Constants that are not equal to zero or
|
||||
// non-constants (e.g., variable accesses, which require data-flow to determine the value)
|
||||
// A zero (null) value typically indicates use of this operation step to initialize
|
||||
// other out parameters in a multi-step initialization.
|
||||
(exists(result.asExpr().getValue()) implies result.asExpr().getValue().toInt() != 0)
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for EVP cipher/decrypt/encrypt 'ex' operations.
|
||||
*/
|
||||
abstract class EvpEXInitializer extends EvpCipherInitializer {
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result = super.getInput(type)
|
||||
or
|
||||
(
|
||||
// Constants that are not equal to zero or
|
||||
// non-constants (e.g., variable accesses, which require data-flow to determine the value)
|
||||
// A zero (null) value typically indicates use of this operation step to initialize
|
||||
// other out parameters in a multi-step initialization.
|
||||
result.asExpr() = this.getArgument(3) and type = KeyIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(4) and type = IVorNonceIO()
|
||||
) and
|
||||
(exists(result.asExpr().getValue()) implies result.asExpr().getValue().toInt() != 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for EVP cipher/decrypt/encrypt 'ex2' operations.
|
||||
*/
|
||||
abstract class EvpEX2Initializer extends EvpCipherInitializer {
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result = super.getInput(type)
|
||||
or
|
||||
result.asExpr() = this.getArgument(2) and type = KeyIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(3) and type = IVorNonceIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Call to an EVP Cipher/Encrypt/Decrypt initialization operation.
|
||||
*/
|
||||
class EvpCipherEXInitCall extends EvpEXInitializer {
|
||||
EvpCipherEXInitCall() {
|
||||
this.getTarget().getName() in ["EVP_EncryptInit_ex", "EVP_DecryptInit_ex", "EVP_CipherInit_ex"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result = super.getInput(type)
|
||||
or
|
||||
// NOTE: for EncryptInit and DecryptInit there is no subtype arg
|
||||
// the subtype is determined automatically by the initializer based on the operation name
|
||||
this.getTarget().getName().toLowerCase().matches("%cipherinit%") and
|
||||
result.asExpr() = this.getArgument(5) and
|
||||
type = KeyOperationSubtypeIO()
|
||||
}
|
||||
}
|
||||
|
||||
class Evp_Cipher_EX2_or_Simple_Init_Call extends EvpEX2Initializer {
|
||||
Evp_Cipher_EX2_or_Simple_Init_Call() {
|
||||
this.getTarget().getName() in [
|
||||
"EVP_EncryptInit_ex2", "EVP_DecryptInit_ex2", "EVP_CipherInit_ex2", "EVP_EncryptInit",
|
||||
"EVP_DecryptInit", "EVP_CipherInit"
|
||||
]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result = super.getInput(type)
|
||||
or
|
||||
this.getTarget().getName().toLowerCase().matches("%cipherinit%") and
|
||||
result.asExpr() = this.getArgument(4) and
|
||||
type = KeyOperationSubtypeIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to EVP_Pkey_encrypt_init, EVP_Pkey_decrypt_init, or their 'ex' variants.
|
||||
*/
|
||||
class EvpPkeyEncryptDecryptInit extends OperationStep {
|
||||
EvpPkeyEncryptDecryptInit() {
|
||||
this.getTarget().getName() in [
|
||||
"EVP_PKEY_encrypt_init", "EVP_PKEY_encrypt_init_ex", "EVP_PKEY_decrypt_init",
|
||||
"EVP_PKEY_decrypt_init_ex"
|
||||
]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = OsslParamIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
class EvpCipherInitSKeyCall extends EvpEX2Initializer {
|
||||
EvpCipherInitSKeyCall() { this.getTarget().getName() = "EVP_CipherInit_SKEY" }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result = super.getInput(type)
|
||||
or
|
||||
result.asExpr() = this.getArgument(5) and
|
||||
type = KeyOperationSubtypeIO()
|
||||
}
|
||||
}
|
||||
|
||||
//EVP_PKEY_encrypt_init
|
||||
/**
|
||||
* A Call to EVP_Cipher/Encrypt/DecryptUpdate.
|
||||
* https://docs.openssl.org/3.2/man3/EVP_CipherUpdate
|
||||
*/
|
||||
class EvpCipherUpdateCall extends OperationStep {
|
||||
EvpCipherUpdateCall() {
|
||||
this.getTarget().getName() in ["EVP_EncryptUpdate", "EVP_DecryptUpdate", "EVP_CipherUpdate"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(3) and type = PlaintextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(1) and type = CiphertextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = UpdateStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A base configuration for all EVP cipher operations.
|
||||
*/
|
||||
abstract class EvpCipherOperationFinalStep extends OperationStep {
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = FinalStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Call to EVP_Cipher.
|
||||
*/
|
||||
class EvpCipherCall extends EvpCipherOperationFinalStep {
|
||||
EvpCipherCall() { this.getTarget().getName() = "EVP_Cipher" }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
super.getInput(type) = result
|
||||
or
|
||||
result.asExpr() = this.getArgument(2) and type = PlaintextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
super.getOutput(type) = result
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = CiphertextIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Call to an EVP Cipher/Encrypt/Decrypt final operation.
|
||||
*/
|
||||
class EvpCipherFinalCall extends EvpCipherOperationFinalStep {
|
||||
EvpCipherFinalCall() {
|
||||
this.getTarget().getName() in [
|
||||
"EVP_EncryptFinal_ex", "EVP_DecryptFinal_ex", "EVP_CipherFinal_ex", "EVP_EncryptFinal",
|
||||
"EVP_DecryptFinal", "EVP_CipherFinal"
|
||||
]
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
super.getOutput(type) = result
|
||||
or
|
||||
result.asDefiningArgument() = this.getArgument(1) and
|
||||
type = CiphertextIO()
|
||||
// TODO: could indicate text lengths here, as well
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a PKEY_encrypt or PKEY_decrypt operation.
|
||||
* https://docs.openssl.org/3.2/man3/EVP_PKEY_decrypt/
|
||||
* https://docs.openssl.org/3.2/man3/EVP_PKEY_encrypt
|
||||
*/
|
||||
class EvpPKeyCipherOperation extends EvpCipherOperationFinalStep {
|
||||
EvpPKeyCipherOperation() {
|
||||
this.getTarget().getName() in ["EVP_PKEY_encrypt", "EVP_PKEY_decrypt"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
super.getInput(type) = result
|
||||
or
|
||||
result.asExpr() = this.getArgument(3) and type = PlaintextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
super.getOutput(type) = result
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = CiphertextIO()
|
||||
// TODO: could indicate text lengths here, as well
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An EVP cipher operation instance.
|
||||
* Any operation step that is a final operation step for EVP cipher operation steps.
|
||||
*/
|
||||
class EvpCipherOperationInstance extends Crypto::KeyOperationInstance instanceof EvpCipherOperationFinalStep
|
||||
{
|
||||
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||
super.getPrimaryAlgorithmValueConsumer() = result
|
||||
}
|
||||
|
||||
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
|
||||
result instanceof Crypto::TEncryptMode and
|
||||
super.getTarget().getName().toLowerCase().matches("%encrypt%")
|
||||
or
|
||||
result instanceof Crypto::TDecryptMode and
|
||||
super.getTarget().getName().toLowerCase().matches("%decrypt%")
|
||||
or
|
||||
super.getTarget().getName().toLowerCase().matches("%cipher%") and
|
||||
resolveKeyOperationSubTypeOperationStep(super
|
||||
.getDominatingInitializersToStep(KeyOperationSubtypeIO())) = result
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
|
||||
super.getDominatingInitializersToStep(IVorNonceIO()).getInput(IVorNonceIO()) = result
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
|
||||
super.getDominatingInitializersToStep(KeyIO()).getInput(KeyIO()) = result
|
||||
}
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
|
||||
super.getOutputStepFlowingToStep(CiphertextIO()).getOutput(CiphertextIO()) = result
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
||||
super.getDominatingInitializersToStep(PlaintextIO()).getInput(PlaintextIO()) = result
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
private import experimental.quantum.Language
|
||||
private import OpenSSLOperationBase
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||
private import semmle.code.cpp.dataflow.new.DataFlow
|
||||
|
||||
class ECKeyGenOperation extends OpenSslOperation, Crypto::KeyGenerationOperationInstance {
|
||||
ECKeyGenOperation() { this.(Call).getTarget().getName() = "EC_KEY_generate_key" }
|
||||
|
||||
override Expr getAlgorithmArg() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Crypto::KeyArtifactType getOutputKeyType() { result = Crypto::TAsymmetricKeyType() }
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() {
|
||||
result.asExpr() = this.(Call).getArgument(0)
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
|
||||
none() // no explicit key size, inferred from algorithm
|
||||
}
|
||||
|
||||
override int getKeySizeFixed() {
|
||||
none()
|
||||
// TODO: marked as none as the operation itself has no key size, it
|
||||
// comes from the algorithm source, but note we could grab the
|
||||
// algorithm source and get the key size (see below).
|
||||
// We may need to reconsider what is the best approach here.
|
||||
// result =
|
||||
// this.getAnAlgorithmValueConsumer()
|
||||
// .getAKnownAlgorithmSource()
|
||||
// .(Crypto::EllipticCurveInstance)
|
||||
// .getKeySize()
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
private import experimental.quantum.Language
|
||||
private import experimental.quantum.OpenSSL.CtxFlow
|
||||
private import OpenSSLOperationBase
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||
|
||||
// TODO: need to add key consumer
|
||||
abstract class Evp_Cipher_Initializer extends EvpKeyOperationSubtypeInitializer,
|
||||
EvpPrimaryAlgorithmInitializer, EvpKeyInitializer, EvpIVInitializer
|
||||
{
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
|
||||
}
|
||||
|
||||
abstract class Evp_EX_Initializer extends Evp_Cipher_Initializer {
|
||||
override Expr getKeyArg() {
|
||||
// Null key indicates the key is not actually set
|
||||
// This pattern can occur during a multi-step initialization
|
||||
// TODO/Note: not flowing 0 to the sink, assuming a direct use of NULL for now
|
||||
result = this.(Call).getArgument(3) and
|
||||
(exists(result.getValue()) implies result.getValue().toInt() != 0)
|
||||
}
|
||||
|
||||
override Expr getIVArg() {
|
||||
// Null IV indicates the IV is not actually set
|
||||
// This occurs given that setting the IV sometimes requires first setting the IV size.
|
||||
// TODO/Note: not flowing 0 to the sink, assuming a direct use of NULL for now
|
||||
result = this.(Call).getArgument(4) and
|
||||
(exists(result.getValue()) implies result.getValue().toInt() != 0)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Evp_EX2_Initializer extends Evp_Cipher_Initializer {
|
||||
override Expr getKeyArg() { result = this.(Call).getArgument(2) }
|
||||
|
||||
override Expr getIVArg() { result = this.(Call).getArgument(3) }
|
||||
}
|
||||
|
||||
class EvpCipherEXInitCall extends Evp_EX_Initializer {
|
||||
EvpCipherEXInitCall() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_EncryptInit_ex", "EVP_DecryptInit_ex", "EVP_CipherInit_ex"
|
||||
]
|
||||
}
|
||||
|
||||
override Expr getKeyOperationSubtypeArg() {
|
||||
// NOTE: for EncryptInit and DecryptInit there is no subtype arg
|
||||
// the subtype is determined automatically by the initializer based on the operation name
|
||||
this.(Call).getTarget().getName().toLowerCase().matches("%cipherinit%") and
|
||||
result = this.(Call).getArgument(5)
|
||||
}
|
||||
}
|
||||
|
||||
// if this.(Call).getTarget().getName().toLowerCase().matches("%encrypt%")
|
||||
// then result instanceof Crypto::TEncryptMode
|
||||
// else
|
||||
// if this.(Call).getTarget().getName().toLowerCase().matches("%decrypt%")
|
||||
// then result instanceof Crypto::TDecryptMode
|
||||
class Evp_Cipher_EX2_or_Simple_Init_Call extends Evp_EX2_Initializer {
|
||||
Evp_Cipher_EX2_or_Simple_Init_Call() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_EncryptInit_ex2", "EVP_DecryptInit_ex2", "EVP_CipherInit_ex2", "EVP_EncryptInit",
|
||||
"EVP_DecryptInit", "EVP_CipherInit"
|
||||
]
|
||||
}
|
||||
|
||||
override Expr getKeyOperationSubtypeArg() {
|
||||
this.(Call).getTarget().getName().toLowerCase().matches("%cipherinit%") and
|
||||
result = this.(Call).getArgument(4)
|
||||
}
|
||||
}
|
||||
|
||||
class Evp_CipherInit_SKey_Call extends Evp_EX2_Initializer {
|
||||
Evp_CipherInit_SKey_Call() { this.(Call).getTarget().getName() = "EVP_CipherInit_SKEY" }
|
||||
|
||||
override Expr getKeyOperationSubtypeArg() { result = this.(Call).getArgument(5) }
|
||||
}
|
||||
|
||||
class Evp_Cipher_Update_Call extends EvpUpdate {
|
||||
Evp_Cipher_Update_Call() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_EncryptUpdate", "EVP_DecryptUpdate", "EVP_CipherUpdate"
|
||||
]
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Expr getInputArg() { result = this.(Call).getArgument(3) }
|
||||
|
||||
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* see: https://docs.openssl.org/master/man3/EVP_EncryptInit/#synopsis
|
||||
* Base configuration for all EVP cipher operations.
|
||||
*/
|
||||
abstract class Evp_Cipher_Operation extends EvpOperation, Crypto::KeyOperationInstance {
|
||||
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
|
||||
result instanceof Crypto::TEncryptMode and
|
||||
this.(Call).getTarget().getName().toLowerCase().matches("%encrypt%")
|
||||
or
|
||||
result instanceof Crypto::TDecryptMode and
|
||||
this.(Call).getTarget().getName().toLowerCase().matches("%decrypt%")
|
||||
or
|
||||
result = this.getInitCall().(EvpKeyOperationSubtypeInitializer).getKeyOperationSubtype() and
|
||||
this.(Call).getTarget().getName().toLowerCase().matches("%cipher%")
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
|
||||
this.getInitCall().(EvpIVInitializer).getIVArg() = result.asExpr()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
|
||||
this.getInitCall().(EvpKeyInitializer).getKeyArg() = result.asExpr()
|
||||
// todo: or track to the EVP_PKEY_CTX_new
|
||||
}
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
|
||||
result = EvpOperation.super.getOutputArtifact()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
||||
result = EvpOperation.super.getInputConsumer()
|
||||
}
|
||||
}
|
||||
|
||||
class Evp_Cipher_Call extends EvpOperation, Evp_Cipher_Operation {
|
||||
Evp_Cipher_Call() { this.(Call).getTarget().getName() = "EVP_Cipher" }
|
||||
|
||||
override Expr getInputArg() { result = this.(Call).getArgument(2) }
|
||||
|
||||
override Expr getAlgorithmArg() {
|
||||
result = this.getInitCall().(EvpPrimaryAlgorithmInitializer).getAlgorithmArg()
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class Evp_Cipher_Final_Call extends EvpFinal, Evp_Cipher_Operation {
|
||||
Evp_Cipher_Final_Call() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_EncryptFinal_ex", "EVP_DecryptFinal_ex", "EVP_CipherFinal_ex", "EVP_EncryptFinal",
|
||||
"EVP_DecryptFinal", "EVP_CipherFinal"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Output is both from update calls and from the final call.
|
||||
*/
|
||||
override Expr getOutputArg() {
|
||||
result = EvpFinal.super.getOutputArg()
|
||||
or
|
||||
result = Evp_Cipher_Operation.super.getOutputArg()
|
||||
}
|
||||
|
||||
override Expr getAlgorithmArg() {
|
||||
result = this.getInitCall().(EvpPrimaryAlgorithmInitializer).getAlgorithmArg()
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* https://docs.openssl.org/3.2/man3/EVP_PKEY_decrypt/
|
||||
* https://docs.openssl.org/3.2/man3/EVP_PKEY_encrypt
|
||||
*/
|
||||
class Evp_PKey_Cipher_Operation extends Evp_Cipher_Operation {
|
||||
Evp_PKey_Cipher_Operation() {
|
||||
this.(Call).getTarget().getName() in ["EVP_PKEY_encrypt", "EVP_PKEY_decrypt"]
|
||||
}
|
||||
|
||||
override Expr getInputArg() { result = this.(Call).getArgument(3) }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Expr getAlgorithmArg() {
|
||||
result = this.getInitCall().(EvpPrimaryAlgorithmInitializer).getAlgorithmArg()
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
|
||||
*/
|
||||
|
||||
private import experimental.quantum.Language
|
||||
private import experimental.quantum.OpenSSL.CtxFlow
|
||||
private import OpenSSLOperationBase
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||
|
||||
class Evp_DigestInit_Variant_Calls extends EvpPrimaryAlgorithmInitializer {
|
||||
Evp_DigestInit_Variant_Calls() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_DigestInit", "EVP_DigestInit_ex", "EVP_DigestInit_ex2"
|
||||
]
|
||||
}
|
||||
|
||||
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class Evp_Digest_Update_Call extends EvpUpdate {
|
||||
Evp_Digest_Update_Call() { this.(Call).getTarget().getName() = "EVP_DigestUpdate" }
|
||||
|
||||
override Expr getInputArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
//https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
|
||||
class Evp_Q_Digest_Operation extends EvpOperation, Crypto::HashOperationInstance {
|
||||
Evp_Q_Digest_Operation() { this.(Call).getTarget().getName() = "EVP_Q_digest" }
|
||||
|
||||
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override EvpInitializer getInitCall() {
|
||||
// This variant of digest does not use an init
|
||||
// and even if it were used, the init would be ignored/undefined
|
||||
none()
|
||||
}
|
||||
|
||||
override Expr getInputArg() { result = this.(Call).getArgument(3) }
|
||||
|
||||
override Expr getOutputArg() { result = this.(Call).getArgument(5) }
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
|
||||
result = EvpOperation.super.getOutputArtifact()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
||||
result = EvpOperation.super.getInputConsumer()
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class Evp_Digest_Operation extends EvpOperation, Crypto::HashOperationInstance {
|
||||
Evp_Digest_Operation() { this.(Call).getTarget().getName() = "EVP_Digest" }
|
||||
|
||||
// There is no context argument for this function
|
||||
override CtxPointerSource getContext() { none() }
|
||||
|
||||
override Expr getAlgorithmArg() { result = this.(Call).getArgument(4) }
|
||||
|
||||
override EvpPrimaryAlgorithmInitializer getInitCall() {
|
||||
// This variant of digest does not use an init
|
||||
// and even if it were used, the init would be ignored/undefined
|
||||
none()
|
||||
}
|
||||
|
||||
override Expr getInputArg() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Expr getOutputArg() { result = this.(Call).getArgument(2) }
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
|
||||
result = EvpOperation.super.getOutputArtifact()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
||||
result = EvpOperation.super.getInputConsumer()
|
||||
}
|
||||
}
|
||||
|
||||
class Evp_Digest_Final_Call extends EvpFinal, Crypto::HashOperationInstance {
|
||||
Evp_Digest_Final_Call() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_DigestFinal", "EVP_DigestFinal_ex", "EVP_DigestFinalXOF"
|
||||
]
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Expr getOutputArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
|
||||
result = EvpFinal.super.getOutputArtifact()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
||||
result = EvpFinal.super.getInputConsumer()
|
||||
}
|
||||
|
||||
override Expr getAlgorithmArg() {
|
||||
result = this.getInitCall().(EvpPrimaryAlgorithmInitializer).getAlgorithmArg()
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
private import experimental.quantum.Language
|
||||
private import experimental.quantum.OpenSSL.CtxFlow
|
||||
private import OpenSSLOperationBase
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||
|
||||
class EvpKeyGenInitialize extends EvpPrimaryAlgorithmInitializer {
|
||||
EvpKeyGenInitialize() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_PKEY_keygen_init",
|
||||
"EVP_PKEY_paramgen_init"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the algorithm argument.
|
||||
* In this case the algorithm is encoded through the context argument.
|
||||
* The context may be directly created from an algorithm consumer,
|
||||
* or from a new operation off of a prior key. Either way,
|
||||
* we will treat this argument as the algorithm argument.
|
||||
*/
|
||||
override Expr getAlgorithmArg() { result = this.getContext() }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class EvpKeyGenOperation extends EvpOperation, Crypto::KeyGenerationOperationInstance {
|
||||
DataFlow::Node keyResultNode;
|
||||
|
||||
EvpKeyGenOperation() {
|
||||
this.(Call).getTarget().getName() in ["EVP_RSA_gen", "EVP_PKEY_Q_keygen"] and
|
||||
keyResultNode.asExpr() = this
|
||||
or
|
||||
this.(Call).getTarget().getName() in ["EVP_PKEY_generate", "EVP_PKEY_keygen"] and
|
||||
keyResultNode.asDefiningArgument() = this.(Call).getArgument(1)
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Expr getAlgorithmArg() {
|
||||
this.(Call).getTarget().getName() = "EVP_PKEY_Q_keygen" and
|
||||
result = this.(Call).getArgument(0)
|
||||
or
|
||||
result = this.getInitCall().(EvpPrimaryAlgorithmInitializer).getAlgorithmArg()
|
||||
}
|
||||
|
||||
override Crypto::KeyArtifactType getOutputKeyType() { result = Crypto::TAsymmetricKeyType() }
|
||||
|
||||
override Expr getInputArg() { none() }
|
||||
|
||||
override Expr getOutputArg() { result = keyResultNode.asExpr() }
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result = keyResultNode }
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
|
||||
this.(Call).getTarget().getName() = "EVP_PKEY_Q_keygen" and
|
||||
result = DataFlow::exprNode(this.(Call).getArgument(3)) and
|
||||
// Arg 3 (0 based) is only a key size if the 'type' parameter is RSA, however,
|
||||
// as a crude approximation, assume that if the type of the argument is not a derived type
|
||||
// the argument must specify a key size (this is to avoid tracing if "rsa" is in the type parameter)
|
||||
not this.(Call).getArgument(3).getType().getUnderlyingType() instanceof DerivedType
|
||||
or
|
||||
this.(Call).getTarget().getName() = "EVP_RSA_gen" and
|
||||
result = DataFlow::exprNode(this.(Call).getArgument(0))
|
||||
or
|
||||
result = DataFlow::exprNode(this.getInitCall().(EvpKeySizeInitializer).getKeySizeArg())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `EVP_PKEY_new_mac_key` that creatse a new generic MAC key.
|
||||
* Signature: EVP_PKEY *EVP_PKEY_new_mac_key(int type, ENGINE *e, const unsigned char *key, int keylen);
|
||||
*/
|
||||
class EvpNewMacKey extends EvpOperation, Crypto::KeyGenerationOperationInstance {
|
||||
DataFlow::Node keyResultNode;
|
||||
|
||||
EvpNewMacKey() {
|
||||
this.(Call).getTarget().getName() = "EVP_PKEY_new_mac_key" and keyResultNode.asExpr() = this
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { none() }
|
||||
|
||||
override Crypto::KeyArtifactType getOutputKeyType() { result = Crypto::TSymmetricKeyType() }
|
||||
|
||||
override Expr getOutputArg() { result = keyResultNode.asExpr() }
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result = keyResultNode }
|
||||
|
||||
override Expr getInputArg() { none() }
|
||||
|
||||
override Expr getAlgorithmArg() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
|
||||
result = DataFlow::exprNode(this.(Call).getArgument(3))
|
||||
}
|
||||
}
|
||||
/// TODO: https://docs.openssl.org/3.0/man3/EVP_PKEY_new/#synopsis
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
import cpp
|
||||
private import experimental.quantum.OpenSSL.CtxFlow
|
||||
private import OpenSSLOperations
|
||||
|
||||
/**
|
||||
@@ -14,49 +13,66 @@ private import OpenSSLOperations
|
||||
* These calls initialize the context from a prior key.
|
||||
* The key may be generated previously, or merely had it's
|
||||
* parameters set (e.g., `EVP_PKEY_paramgen`).
|
||||
* NOTE: for the case of `EVP_PKEY_paramgen`, these calls
|
||||
* are encoded as context passthroughs, and any operation
|
||||
* will get all associated initializers for the paramgen
|
||||
* at the final keygen operation automatically.
|
||||
*/
|
||||
class EvpNewKeyCtx extends EvpKeyInitializer {
|
||||
class EvpNewKeyCtx extends OperationStep instanceof Call {
|
||||
Expr keyArg;
|
||||
|
||||
EvpNewKeyCtx() {
|
||||
this.(Call).getTarget().getName() = "EVP_PKEY_CTX_new" and
|
||||
keyArg = this.(Call).getArgument(0)
|
||||
this.getTarget().getName() = "EVP_PKEY_CTX_new" and
|
||||
keyArg = this.getArgument(0)
|
||||
or
|
||||
this.(Call).getTarget().getName() = "EVP_PKEY_CTX_new_from_pkey" and
|
||||
keyArg = this.(Call).getArgument(1)
|
||||
this.getTarget().getName() = "EVP_PKEY_CTX_new_from_pkey" and
|
||||
keyArg = this.getArgument(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Context is returned
|
||||
*/
|
||||
override CtxPointerSource getContext() { result = this }
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = keyArg and type = KeyIO()
|
||||
or
|
||||
this.getTarget().getName() = "EVP_PKEY_CTX_new_from_pkey" and
|
||||
result.asExpr() = this.getArgument(0) and
|
||||
type = OsslLibContextIO()
|
||||
}
|
||||
|
||||
override Expr getKeyArg() { result = keyArg }
|
||||
override DataFlow::Node getOutput(IOType type) { result.asExpr() = this and type = ContextIO() }
|
||||
|
||||
override OperationStepType getStepType() { result = ContextCreationStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to "EVP_PKEY_CTX_set_ec_paramgen_curve_nid".
|
||||
* Note that this is a primary algorithm as the pattenr is to specify an "EC" context,
|
||||
* then set the specific curve later. Although the curve is set later, it is the primary
|
||||
* algorithm intended for an operation.
|
||||
*/
|
||||
class EvpCtxSetPrimaryAlgorithmInitializer extends EvpPrimaryAlgorithmInitializer {
|
||||
EvpCtxSetPrimaryAlgorithmInitializer() {
|
||||
this.(Call).getTarget().getName() = "EVP_PKEY_CTX_set_ec_paramgen_curve_nid"
|
||||
class EvpCtxSetEcParamgenCurveNidInitializer extends OperationStep {
|
||||
EvpCtxSetEcParamgenCurveNidInitializer() {
|
||||
this.getTarget().getName() = "EVP_PKEY_CTX_set_ec_paramgen_curve_nid"
|
||||
}
|
||||
|
||||
override Expr getAlgorithmArg() { result = this.(Call).getArgument(1) }
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = PrimaryAlgorithmIO()
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
class EvpCtxSetHashAlgorithmInitializer extends EvpHashAlgorithmInitializer {
|
||||
EvpCtxSetHashAlgorithmInitializer() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
/**
|
||||
* A call to the following:
|
||||
* - `EVP_PKEY_CTX_set_signature_md`
|
||||
* - `EVP_PKEY_CTX_set_rsa_mgf1_md_name`
|
||||
* - `EVP_PKEY_CTX_set_rsa_mgf1_md`
|
||||
* - `EVP_PKEY_CTX_set_rsa_oaep_md_name`
|
||||
* - `EVP_PKEY_CTX_set_rsa_oaep_md`
|
||||
* - `EVP_PKEY_CTX_set_dsa_paramgen_md`
|
||||
* - `EVP_PKEY_CTX_set_dh_kdf_md`
|
||||
* - `EVP_PKEY_CTX_set_ecdh_kdf_md`
|
||||
*/
|
||||
class EvpCtxSetHashInitializer extends OperationStep {
|
||||
EvpCtxSetHashInitializer() {
|
||||
this.getTarget().getName() in [
|
||||
"EVP_PKEY_CTX_set_signature_md", "EVP_PKEY_CTX_set_rsa_mgf1_md_name",
|
||||
"EVP_PKEY_CTX_set_rsa_mgf1_md", "EVP_PKEY_CTX_set_rsa_oaep_md_name",
|
||||
"EVP_PKEY_CTX_set_rsa_oaep_md", "EVP_PKEY_CTX_set_dsa_paramgen_md",
|
||||
@@ -64,56 +80,95 @@ class EvpCtxSetHashAlgorithmInitializer extends EvpHashAlgorithmInitializer {
|
||||
]
|
||||
}
|
||||
|
||||
override Expr getHashAlgorithmArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class EvpCtxSetKeySizeInitializer extends EvpKeySizeInitializer {
|
||||
Expr arg;
|
||||
|
||||
EvpCtxSetKeySizeInitializer() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_PKEY_CTX_set_rsa_keygen_bits", "EVP_PKEY_CTX_set_dsa_paramgen_bits",
|
||||
"EVP_CIPHER_CTX_set_key_length"
|
||||
] and
|
||||
arg = this.(Call).getArgument(1)
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
this.(Call).getTarget().getName() = "EVP_PKEY_CTX_set_mac_key" and
|
||||
arg = this.(Call).getArgument(2)
|
||||
result.asExpr() = this.getArgument(1) and type = HashAlgorithmIO()
|
||||
}
|
||||
|
||||
override Expr getKeySizeArg() { result = arg }
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
class EvpCtxSetKeyInitializer extends EvpKeyInitializer {
|
||||
EvpCtxSetKeyInitializer() { this.(Call).getTarget().getName() = "EVP_PKEY_CTX_set_mac_key" }
|
||||
|
||||
override Expr getKeyArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class EvpCtxSetPaddingInitializer extends EvpPaddingInitializer {
|
||||
EvpCtxSetPaddingInitializer() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_PKEY_CTX_set_rsa_padding", "EVP_CIPHER_CTX_set_padding"
|
||||
/**
|
||||
* A call to `EVP_PKEY_CTX_set_rsa_keygen_bits`, `EVP_PKEY_CTX_set_dsa_paramgen_bits`,
|
||||
* or `EVP_CIPHER_CTX_set_key_length`.
|
||||
*/
|
||||
class EvpCtxSetKeySizeInitializer extends OperationStep {
|
||||
EvpCtxSetKeySizeInitializer() {
|
||||
this.getTarget().getName() in [
|
||||
"EVP_PKEY_CTX_set_rsa_keygen_bits", "EVP_PKEY_CTX_set_dsa_paramgen_bits",
|
||||
"EVP_CIPHER_CTX_set_key_length"
|
||||
]
|
||||
}
|
||||
|
||||
override Expr getPaddingArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class EvpCtxSetSaltLengthInitializer extends EvpSaltLengthInitializer {
|
||||
EvpCtxSetSaltLengthInitializer() {
|
||||
this.(Call).getTarget().getName() = "EVP_PKEY_CTX_set_rsa_pss_saltlen"
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = KeySizeIO()
|
||||
}
|
||||
|
||||
override Expr getSaltLengthArg() { result = this.(Call).getArgument(1) }
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
class EvpCtxSetMacKeyInitializer extends OperationStep {
|
||||
EvpCtxSetMacKeyInitializer() { this.getTarget().getName() = "EVP_PKEY_CTX_set_mac_key" }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(2) and type = KeySizeIO()
|
||||
or
|
||||
// the raw key that is configured into the output key
|
||||
result.asExpr() = this.getArgument(1) and type = KeyIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
class EvpCtxSetPaddingInitializer extends OperationStep {
|
||||
EvpCtxSetPaddingInitializer() {
|
||||
this.getTarget().getName() in ["EVP_PKEY_CTX_set_rsa_padding", "EVP_CIPHER_CTX_set_padding"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = PaddingAlgorithmIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
class EvpCtxSetSaltLengthInitializer extends OperationStep {
|
||||
EvpCtxSetSaltLengthInitializer() {
|
||||
this.getTarget().getName() = "EVP_PKEY_CTX_set_rsa_pss_saltlen"
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = SaltLengthIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
/**
|
||||
* Provides classes for modeling OpenSSL's EVP signature operations
|
||||
*/
|
||||
|
||||
private import experimental.quantum.Language
|
||||
private import experimental.quantum.OpenSSL.AvcFlow
|
||||
private import experimental.quantum.OpenSSL.CtxFlow
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||
private import experimental.quantum.OpenSSL.Operations.OpenSSLOperations
|
||||
|
||||
// TODO: verification functions
|
||||
class EvpSignatureDigestInitializer extends EvpHashAlgorithmInitializer {
|
||||
Expr arg;
|
||||
|
||||
EvpSignatureDigestInitializer() {
|
||||
this.(Call).getTarget().getName() in ["EVP_DigestSignInit_ex", "EVP_DigestSignInit"] and
|
||||
arg = this.(Call).getArgument(2)
|
||||
or
|
||||
this.(Call).getTarget().getName() in ["EVP_SignInit", "EVP_SignInit_ex"] and
|
||||
arg = this.(Call).getArgument(1)
|
||||
}
|
||||
|
||||
override Expr getHashAlgorithmArg() { result = arg }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class EvpSignatureKeyInitializer extends EvpKeyInitializer {
|
||||
Expr arg;
|
||||
|
||||
EvpSignatureKeyInitializer() {
|
||||
this.(Call).getTarget().getName() = "EVP_DigestSignInit_ex" and
|
||||
arg = this.(Call).getArgument(5)
|
||||
or
|
||||
this.(Call).getTarget().getName() = "EVP_DigestSignInit" and
|
||||
arg = this.(Call).getArgument(4)
|
||||
}
|
||||
|
||||
override Expr getKeyArg() { result = arg }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class EvpSignaturePrimaryAlgorithmInitializer extends EvpPrimaryAlgorithmInitializer {
|
||||
Expr arg;
|
||||
|
||||
EvpSignaturePrimaryAlgorithmInitializer() {
|
||||
// signature algorithm
|
||||
this.(Call).getTarget().getName() in ["EVP_PKEY_sign_init_ex2", "EVP_PKEY_sign_message_init"] and
|
||||
arg = this.(Call).getArgument(1)
|
||||
or
|
||||
// configuration through the context argument
|
||||
this.(Call).getTarget().getName() in ["EVP_PKEY_sign_init", "EVP_PKEY_sign_init_ex"] and
|
||||
arg = this.getContext()
|
||||
}
|
||||
|
||||
override Expr getAlgorithmArg() { result = arg }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
class Evp_Signature_Update_Call extends EvpUpdate {
|
||||
Evp_Signature_Update_Call() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_DigestSignUpdate", "EVP_SignUpdate", "EVP_PKEY_sign_message_update"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Input is the message to sign.
|
||||
*/
|
||||
override Expr getInputArg() { result = this.(Call).getArgument(1) }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* We model output explicit output arguments as predicate to use it in constructors.
|
||||
* The predicate must cover all EVP_Signature_Operation subclasses.
|
||||
*/
|
||||
pragma[inline]
|
||||
private Expr signatureOperationOutputArg(Call call) {
|
||||
if call.getTarget().getName() = "EVP_SignFinal_ex"
|
||||
then result = call.getArgument(2)
|
||||
else result = call.getArgument(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* The base configuration for all EVP signature operations.
|
||||
*/
|
||||
abstract class EvpSignatureOperation extends EvpOperation, Crypto::SignatureOperationInstance {
|
||||
EvpSignatureOperation() {
|
||||
this.(Call).getTarget().getName().matches("EVP_%") and
|
||||
// NULL output argument means the call is to get the size of the signature and such call is not an operation
|
||||
(
|
||||
not exists(signatureOperationOutputArg(this).getValue())
|
||||
or
|
||||
signatureOperationOutputArg(this).getValue() != "0"
|
||||
)
|
||||
}
|
||||
|
||||
Expr getHashAlgorithmArg() {
|
||||
this.getInitCall().(EvpHashAlgorithmInitializer).getHashAlgorithmArg() = result
|
||||
}
|
||||
|
||||
override Expr getAlgorithmArg() {
|
||||
this.getInitCall().(EvpPrimaryAlgorithmInitializer).getAlgorithmArg() = result
|
||||
}
|
||||
|
||||
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() {
|
||||
AvcToCallArgFlow::flow(result.(OpenSslAlgorithmValueConsumer).getResultNode(),
|
||||
DataFlow::exprNode(this.getHashAlgorithmArg()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Signing, verification or unknown.
|
||||
*/
|
||||
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
|
||||
// TODO: if this KeyOperationSubtype does not match initialization call's KeyOperationSubtype then we found a bug
|
||||
if this.(Call).getTarget().getName().toLowerCase().matches("%sign%")
|
||||
then result instanceof Crypto::TSignMode
|
||||
else
|
||||
if this.(Call).getTarget().getName().toLowerCase().matches("%verify%")
|
||||
then result instanceof Crypto::TVerifyMode
|
||||
else result instanceof Crypto::TUnknownKeyOperationMode
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
|
||||
// TODO: some signing operations may have explicit nonce generators
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Keys provided in the initialization call or in a context are found by this method.
|
||||
* Keys in explicit arguments are found by overridden methods in extending classes.
|
||||
*/
|
||||
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
|
||||
result = DataFlow::exprNode(this.getInitCall().(EvpKeyInitializer).getKeyArg())
|
||||
}
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
|
||||
result = EvpOperation.super.getOutputArtifact()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
||||
result = EvpOperation.super.getInputConsumer()
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: only signing operations for now, change when verificaiton is added
|
||||
*/
|
||||
override Crypto::ConsumerInputDataFlowNode getSignatureConsumer() { none() }
|
||||
}
|
||||
|
||||
class Evp_Signature_Call extends EvpSignatureOperation {
|
||||
Evp_Signature_Call() { this.(Call).getTarget().getName() in ["EVP_DigestSign", "EVP_PKEY_sign"] }
|
||||
|
||||
/**
|
||||
* Output is the signature.
|
||||
*/
|
||||
override Expr getOutputArg() { result = signatureOperationOutputArg(this) }
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
|
||||
/**
|
||||
* Input is the message to sign.
|
||||
*/
|
||||
override Expr getInputArg() { result = this.(Call).getArgument(3) }
|
||||
}
|
||||
|
||||
class Evp_Signature_Final_Call extends EvpFinal, EvpSignatureOperation {
|
||||
Evp_Signature_Final_Call() {
|
||||
this.(Call).getTarget().getName() in [
|
||||
"EVP_DigestSignFinal",
|
||||
"EVP_SignFinal_ex",
|
||||
"EVP_SignFinal",
|
||||
"EVP_PKEY_sign_message_final"
|
||||
]
|
||||
}
|
||||
|
||||
override CtxPointerSource getContext() { result = this.(Call).getArgument(0) }
|
||||
|
||||
override Expr getAlgorithmArg() {
|
||||
this.getInitCall().(EvpPrimaryAlgorithmInitializer).getAlgorithmArg() = result
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
|
||||
// key provided as an argument
|
||||
this.(Call).getTarget().getName() in ["EVP_SignFinal", "EVP_SignFinal_ex"] and
|
||||
result = DataFlow::exprNode(this.(Call).getArgument(3))
|
||||
or
|
||||
// or find key in the initialization call
|
||||
result = EvpSignatureOperation.super.getKeyConsumer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Output is the signature.
|
||||
*/
|
||||
override Expr getOutputArg() { result = signatureOperationOutputArg(this) }
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
|
||||
*/
|
||||
|
||||
private import experimental.quantum.Language
|
||||
private import OpenSSLOperationBase
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||
|
||||
/**
|
||||
* A call to and EVP digest initializer, such as:
|
||||
* - `EVP_DigestInit`
|
||||
* - `EVP_DigestInit_ex`
|
||||
* - `EVP_DigestInit_ex2`
|
||||
*/
|
||||
class EvpDigestInitVariantCalls extends OperationStep instanceof Call {
|
||||
EvpDigestInitVariantCalls() {
|
||||
this.getTarget().getName() in ["EVP_DigestInit", "EVP_DigestInit_ex", "EVP_DigestInit_ex2"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = PrimaryAlgorithmIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and
|
||||
type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `EVP_DigestUpdate`.
|
||||
*/
|
||||
class EvpDigestUpdateCall extends OperationStep instanceof Call {
|
||||
EvpDigestUpdateCall() { this.getTarget().getName() = "EVP_DigestUpdate" }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(1) and type = PlaintextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and
|
||||
type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = UpdateStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for final digest operations.
|
||||
*/
|
||||
abstract class EvpFinalDigestOperationStep extends OperationStep {
|
||||
override OperationStepType getStepType() { result = FinalStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `EVP_Q_digest`
|
||||
* https://docs.openssl.org/3.0/man3/EVP_DigestInit/#synopsis
|
||||
*/
|
||||
class EvpQDigestOperation extends EvpFinalDigestOperationStep instanceof Call {
|
||||
EvpQDigestOperation() { this.getTarget().getName() = "EVP_Q_digest" }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(1) and type = PrimaryAlgorithmIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(3) and type = PlaintextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and
|
||||
type = ContextIO()
|
||||
or
|
||||
result.asDefiningArgument() = this.getArgument(5) and type = DigestIO()
|
||||
}
|
||||
}
|
||||
|
||||
class EvpDigestOperation extends EvpFinalDigestOperationStep instanceof Call {
|
||||
EvpDigestOperation() { this.getTarget().getName() = "EVP_Digest" }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(4) and type = PrimaryAlgorithmIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(0) and type = PlaintextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asDefiningArgument() = this.getArgument(2) and type = DigestIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to EVP_DigestFinal variants
|
||||
*/
|
||||
class EvpDigestFinalCall extends EvpFinalDigestOperationStep instanceof Call {
|
||||
EvpDigestFinalCall() {
|
||||
this.getTarget().getName() in ["EVP_DigestFinal", "EVP_DigestFinal_ex", "EVP_DigestFinalXOF"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and
|
||||
type = ContextIO()
|
||||
or
|
||||
result.asDefiningArgument() = this.getArgument(1) and type = DigestIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An openssl digest final hash operation instance
|
||||
*/
|
||||
class EvpDigestFinalOperationInstance extends Crypto::HashOperationInstance instanceof EvpFinalDigestOperationStep
|
||||
{
|
||||
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||
super.getPrimaryAlgorithmValueConsumer() = result
|
||||
}
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
|
||||
super.getOutputStepFlowingToStep(DigestIO()).getOutput(DigestIO()) = result
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
||||
super.getDominatingInitializersToStep(PlaintextIO()).getInput(PlaintextIO()) = result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
private import experimental.quantum.Language
|
||||
private import OpenSSLOperationBase
|
||||
private import experimental.quantum.OpenSSL.AlgorithmValueConsumers.OpenSSLAlgorithmValueConsumers
|
||||
|
||||
/**
|
||||
* A call to EC_KEY_generate_key, which is used to generate an EC key pair.
|
||||
* Note: this is an operation, though the input parameter is a "EC_KEY*".
|
||||
* EC_KEY is really an empty context for a key that hasn't been generated, hence
|
||||
* we consider this an operation generating a key and not accepting a key input.
|
||||
*/
|
||||
class ECKeyGen extends OperationStep instanceof Call {
|
||||
//, Crypto::KeyGenerationOperationInstance {
|
||||
ECKeyGen() { this.(Call).getTarget().getName() = "EC_KEY_generate_key" }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.(Call).getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) { result.asExpr() = this and type = KeyIO() }
|
||||
|
||||
override OperationStepType getStepType() { result = ContextCreationStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to EVP_PKEY_keygen_init or EVP_PKEY_paramgen_init.
|
||||
*/
|
||||
class EvpKeyGenInitialize extends OperationStep {
|
||||
EvpKeyGenInitialize() {
|
||||
this.getTarget().getName() in [
|
||||
"EVP_PKEY_keygen_init",
|
||||
"EVP_PKEY_paramgen_init"
|
||||
]
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override OperationStepType getStepType() { result = InitializerStep() }
|
||||
}
|
||||
|
||||
abstract class KeyGenFinalOperationStep extends OperationStep {
|
||||
override OperationStepType getStepType() { result = FinalStep() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `EVP_PKEY_Q_keygen`
|
||||
*/
|
||||
class EvpPKeyQKeyGen extends KeyGenFinalOperationStep instanceof Call {
|
||||
EvpPKeyQKeyGen() { this.getTarget().getName() = "EVP_PKEY_Q_keygen" }
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
result.asExpr() = this and type = KeyIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
// When arg 3 is a derived type, it is a curve name, otherwise it is a key size for RSA if provided
|
||||
// and arg 2 is the algorithm type
|
||||
this.getArgument(3).getType().getUnderlyingType() instanceof DerivedType and
|
||||
result.asExpr() = this.getArgument(3) and
|
||||
type = PrimaryAlgorithmIO()
|
||||
or
|
||||
not this.getArgument(3).getType().getUnderlyingType() instanceof DerivedType and
|
||||
result.asExpr() = this.getArgument(2) and
|
||||
type = PrimaryAlgorithmIO()
|
||||
or
|
||||
not this.getArgument(3).getType().getUnderlyingType() instanceof DerivedType and
|
||||
result.asExpr() = this.getArgument(3) and
|
||||
type = KeySizeIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `EVP_RSA_gen`
|
||||
*/
|
||||
class EvpRsaGen extends KeyGenFinalOperationStep instanceof Call {
|
||||
EvpRsaGen() { this.getTarget().getName() = "EVP_RSA_gen" }
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) { result.asExpr() = this and type = KeyIO() }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = KeySizeIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to RSA_generate_key
|
||||
*/
|
||||
class RsaGenerateKey extends KeyGenFinalOperationStep instanceof Call {
|
||||
RsaGenerateKey() { this.getTarget().getName() = "RSA_generate_key" }
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) { result.asExpr() = this and type = KeyIO() }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = KeySizeIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to RSA_generate_key_ex
|
||||
*/
|
||||
class RsaGenerateKeyEx extends KeyGenFinalOperationStep instanceof Call {
|
||||
RsaGenerateKeyEx() { this.getTarget().getName() = "RSA_generate_key_ex" }
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asDefiningArgument() = this.getArgument(0) and type = KeyIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
// arg 0 comes in as a blank RSA key, which we consider a context,
|
||||
// on output it is considered a key
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `EVP_PKEY_generate` or `EVP_PKEY_keygen`.
|
||||
*/
|
||||
class EvpPkeyGen extends KeyGenFinalOperationStep instanceof Call {
|
||||
EvpPkeyGen() { this.getTarget().getName() in ["EVP_PKEY_generate", "EVP_PKEY_keygen"] }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asDefiningArgument() = this.getArgument(1) and type = KeyIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `EVP_PKEY_new_mac_key` that creates a new generic MAC key.
|
||||
* - EVP_PKEY *EVP_PKEY_new_mac_key(int type, ENGINE *e, const unsigned char *key, int keylen);
|
||||
*/
|
||||
class EvpNewMacKey extends KeyGenFinalOperationStep {
|
||||
EvpNewMacKey() { this.getTarget().getName() = "EVP_PKEY_new_mac_key" }
|
||||
|
||||
override DataFlow::Node getInput(IOType type) {
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
or
|
||||
// the raw key that is configured into the output key
|
||||
result.asExpr() = this.getArgument(2) and type = KeyIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(3) and type = KeySizeIO()
|
||||
}
|
||||
|
||||
override DataFlow::Node getOutput(IOType type) {
|
||||
result.asExpr() = this and type = KeyIO()
|
||||
or
|
||||
result.asExpr() = this.getArgument(0) and type = ContextIO()
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: https://docs.openssl.org/3.0/man3/EVP_PKEY_new/#synopsis
|
||||
/**
|
||||
* An `KeyGenerationOperationInstance` for the for all key gen final operation steps.
|
||||
*/
|
||||
class KeyGenOperationInstance extends Crypto::KeyGenerationOperationInstance instanceof KeyGenFinalOperationStep
|
||||
{
|
||||
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||
super.getPrimaryAlgorithmValueConsumer() = result
|
||||
}
|
||||
|
||||
override Crypto::KeyArtifactType getOutputKeyType() { result = Crypto::TAsymmetricKeyType() }
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() {
|
||||
super.getOutputStepFlowingToStep(KeyIO()).getOutput(KeyIO()) = result
|
||||
}
|
||||
|
||||
override predicate hasKeyValueConsumer() {
|
||||
exists(OperationStep s | s.flowsToOperationStep(this) and s.setsValue(KeyIO()))
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
|
||||
super.getDominatingInitializersToStep(KeySizeIO()).getInput(KeySizeIO()) = result
|
||||
}
|
||||
|
||||
override int getKeySizeFixed() {
|
||||
none()
|
||||
// TODO: marked as none as the operation itself has no key size, it
|
||||
// comes from the algorithm source, but note we could grab the
|
||||
// algorithm source and get the key size (see below).
|
||||
// We may need to reconsider what is the best approach here.
|
||||
// result =
|
||||
// this.getAnAlgorithmValueConsumer()
|
||||
// .getAKnownAlgorithmSource()
|
||||
// .(Crypto::EllipticCurveInstance)
|
||||
// .getKeySize()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeyValueConsumer() {
|
||||
super.getDominatingInitializersToStep(KeyIO()).getInput(KeyIO()) = result
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user