diff --git a/.codeqlmanifest.json b/.codeqlmanifest.json
index ac468d9838..b7d2d5604c 100644
--- a/.codeqlmanifest.json
+++ b/.codeqlmanifest.json
@@ -1 +1,9 @@
-{ "provide": [ "codeql_modules/*/.codeqlmanifest.json", "cpp/.codeqlmanifest.json", "c/.codeqlmanifest.json"] }
+{
+ "provide": [
+ "cpp/*/src/qlpack.yml",
+ "cpp/*/test/qlpack.yml",
+ "c/*/src/qlpack.yml",
+ "c/*/test/qlpack.yml",
+ "scripts/generate_modules/queries/qlpack.yml"
+ ]
+}
\ No newline at end of file
diff --git a/.github/actions/check-permissions/action.yml b/.github/actions/check-permissions/action.yml
new file mode 100644
index 0000000000..b47466d080
--- /dev/null
+++ b/.github/actions/check-permissions/action.yml
@@ -0,0 +1,49 @@
+name: Check current actor permissions
+description: |
+ Checks whether the current actor has the specified permssions
+inputs:
+ minimum-permission:
+ description: |
+ The minimum required permission. One of: read, write, admin
+ required: true
+outputs:
+ has-permission:
+ description: "Whether the actor had the minimum required permission"
+ value: ${{ steps.check-permission.outputs.has-permission }}
+
+runs:
+ using: composite
+ steps:
+ - uses: actions/github-script@v7
+ id: check-permission
+ env:
+ INPUT_MINIMUM-PERMISSION: ${{ inputs.minimum-permission }}
+ with:
+ script: |
+ // Valid permissions are none, read, write, admin (legacy base permissions)
+ const permissionsRanking = ["none", "read", "write", "admin"];
+
+ // Note: core.getInput doesn't work by default in a composite action - in this case
+ // it would try to fetch the input to the github-script instead of the action
+ // itself. Instead, we set the appropriate magic env var with the actions input.
+ // See: https://github.com/actions/runner/issues/665
+ const minimumPermission = core.getInput('minimum-permission');
+ if (!permissionsRanking.includes(minimumPermission)) {
+ core.setFailed(`Invalid minimum permission: ${minimumPermission}`);
+ return;
+ }
+
+ const { data : { permission : actorPermission } } = await github.rest.repos.getCollaboratorPermissionLevel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ username: context.actor
+ });
+
+ // Confirm whether the actor permission is at least the selected permission
+ const hasPermission = permissionsRanking.indexOf(minimumPermission) <= permissionsRanking.indexOf(actorPermission) ? "1" : "";
+ core.setOutput('has-permission', hasPermission);
+ if (!hasPermission) {
+ core.info(`Current actor (${context.actor}) does not have the minimum required permission '${minimumPermission}' (has '${actorPermission}')`);
+ } else {
+ core.info(`Current actor (${context.actor}) has the minimum required permission '${minimumPermission}' (has '${actorPermission}')`);
+ }
diff --git a/.github/actions/install-codeql-packs/action.yml b/.github/actions/install-codeql-packs/action.yml
new file mode 100644
index 0000000000..2e6d5f1a2e
--- /dev/null
+++ b/.github/actions/install-codeql-packs/action.yml
@@ -0,0 +1,25 @@
+name: Install CodeQL library pack dependencies
+description: |
+ Downloads any necessary CodeQL library packs needed by packs in the repo.
+inputs:
+ cli_path:
+ description: |
+ The path to the CodeQL CLI directory.
+ required: false
+
+ mode:
+ description: |
+ The `--mode` option to `codeql pack install`.
+ required: true
+ default: verify
+
+runs:
+ using: composite
+ steps:
+ - name: Install CodeQL library packs
+ shell: bash
+ env:
+ CODEQL_CLI: ${{ inputs.cli_path }}
+ run: |
+ PATH=$PATH:$CODEQL_CLI
+ python scripts/install-packs.py --mode ${{ inputs.mode }}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..73f11c1f47
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ # Check for updates to GitHub Actions every week
+ interval: "weekly"
\ No newline at end of file
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index ae14ef5b59..4d0c170187 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -32,6 +32,12 @@ _**Author:**_ Is a change note required?
- [ ] Yes
- [ ] No
+🚨🚨🚨
+_**Reviewer:**_ Confirm that format of *shared* queries (not the .qll file, the
+.ql file that imports it) is valid by running them within VS Code.
+ - [ ] Confirmed
+
+
_**Reviewer:**_ Confirm that either a change note is not required or the change note is required and has been added.
- [ ] Confirmed
diff --git a/.github/touch b/.github/touch
deleted file mode 100644
index 8b13789179..0000000000
--- a/.github/touch
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml
deleted file mode 100644
index aa3bb668ca..0000000000
--- a/.github/workflows/bump-version.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: 📦 Bump Workflow
-
-on:
- workflow_dispatch:
- inputs:
- new_version:
- description: |
- The version to update to (eg: 2.6.0 or 2.6.0-dev do not include `v`).
- required: true
-jobs:
-
- apply-version-bump:
- runs-on: ubuntu-latest
- name: Apply Version Bump
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Apply Bump
- shell: bash
- run: |
- bash ./scripts/bump_version.sh ${{ github.event.inputs.new_version }}
-
- - name: Create Pull Request
- uses: peter-evans/create-pull-request@v4
- with:
- title: "Release Engineering: Version bump to ${{ github.event.inputs.new_version }}."
- body: "This PR updates codeql-coding-standards to version ${{ github.event.inputs.new_version }}."
- commit-message: "Version bump to ${{ github.event.inputs.new_version }}."
- team-reviewers: github/codeql-coding-standards
- delete-branch: true
- branch: "automation/version-bump-${{ github.event.inputs.new_version }}"
diff --git a/.github/workflows/code-scanning-pack-gen.yml b/.github/workflows/code-scanning-pack-gen.yml
index 0814e059e8..678b3be403 100644
--- a/.github/workflows/code-scanning-pack-gen.yml
+++ b/.github/workflows/code-scanning-pack-gen.yml
@@ -1,51 +1,52 @@
name: Code Scanning Query Pack Generation
on:
+ merge_group:
+ types: [checks_requested]
pull_request:
branches:
- main
- - "rc/**"
- next
-
+ - "rc/**"
push:
branches:
- main
- - "rc/**"
- next
+ - "rc/**"
env:
XARGS_MAX_PROCS: 4
jobs:
+
prepare-code-scanning-pack-matrix:
name: Prepare CodeQL Code Scanning pack matrix
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
outputs:
matrix: ${{ steps.export-code-scanning-pack-matrix.outputs.matrix }}
steps:
- name: Checkout repository
- uses: actions/checkout@v2
-
+ uses: actions/checkout@v4
- name: Export Code Scanning pack matrix
id: export-code-scanning-pack-matrix
run: |
- echo "::set-output name=matrix::$(
+ echo "matrix=$(
jq --compact-output '.supported_environment | {include: .}' supported_codeql_configs.json
- )"
+ )" >> $GITHUB_OUTPUT
create-code-scanning-pack:
name: Create Code Scanning pack
needs: prepare-code-scanning-pack-matrix
- runs-on: ubuntu-20.04-xl
+ runs-on: ubuntu-latest-xl
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.prepare-code-scanning-pack-matrix.outputs.matrix) }}
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Cache CodeQL
id: cache-codeql
- uses: actions/cache@v2.1.3
+ uses: actions/cache@v4
with:
path: ${{ github.workspace }}/codeql_home
key: codeql-home-${{ matrix.os }}-${{ matrix.codeql_cli }}-${{ matrix.codeql_standard_library }}
@@ -59,18 +60,37 @@ jobs:
codeql-home: ${{ github.workspace }}/codeql_home
add-to-path: false
+ - name: Install CodeQL packs
+ uses: ./.github/actions/install-codeql-packs
+ with:
+ cli_path: ${{ github.workspace }}/codeql_home/codeql
+
+ - name: Determine ref for external help files
+ id: determine-ref
+ run: |
+ if [[ $GITHUB_EVENT_NAME == "pull_request" ]]; then
+ EXTERNAL_HELP_REF="${{ github.event.pull_request.base.ref }}"
+ elif [[ $GITHUB_EVENT_NAME == "merge_group" ]]; then
+ EXTERNAL_HELP_REF="${{ github.event.merge_group.base_ref }}"
+ else
+ EXTERNAL_HELP_REF="$GITHUB_REF"
+ fi
+ echo "EXTERNAL_HELP_REF=$EXTERNAL_HELP_REF" >> "$GITHUB_ENV"
+ echo "Using ref $EXTERNAL_HELP_REF for external help files."
+
- name: Checkout external help files
- continue-on-error: true
id: checkout-external-help-files
- uses: actions/checkout@v2
+ # PRs from forks and dependabot do not have access to an appropriate token for cloning the help files repos
+ if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }}
+ uses: actions/checkout@v4
with:
ssh-key: ${{ secrets.CODEQL_CODING_STANDARDS_HELP_KEY }}
repository: "github/codeql-coding-standards-help"
- ref: ${{ github.head_ref }}
+ ref: ${{ env.EXTERNAL_HELP_REF }}
path: external-help-files
- name: Include external help files
- if: ${{ steps.checkout-external-help-files.outcome == 'success' }}
+ if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]'&& steps.checkout-external-help-files.outcome == 'success' }}
run: |
pushd external-help-files
find . -name '*.md' -exec rsync -av --relative {} "$GITHUB_WORKSPACE" \;
@@ -81,15 +101,36 @@ jobs:
CODEQL_HOME: ${{ github.workspace }}/codeql_home
run: |
PATH=$PATH:$CODEQL_HOME/codeql
-
- codeql query compile --search-path cpp --threads 0 cpp
- codeql query compile --search-path c --search-path cpp --threads 0 c
+ # Precompile all queries, and use a compilation cache larger than default
+ # to ensure we cache all the queries for later steps
+ codeql query compile --precompile --threads 0 --compilation-cache-size=1024 cpp c
cd ..
- zip -r codeql-coding-standards/code-scanning-cpp-query-pack.zip codeql-coding-standards/c/ codeql-coding-standards/cpp/ codeql-coding-standards/.codeqlmanifest.json codeql-coding-standards/supported_codeql_configs.json codeql-coding-standards/scripts/deviations codeql-coding-standards/scripts/reports
+ zip -r codeql-coding-standards/code-scanning-cpp-query-pack.zip codeql-coding-standards/c/ codeql-coding-standards/cpp/ codeql-coding-standards/.codeqlmanifest.json codeql-coding-standards/supported_codeql_configs.json codeql-coding-standards/scripts/configuration codeql-coding-standards/scripts/reports codeql-coding-standards/scripts/shared codeql-coding-standards/scripts/guideline_recategorization codeql-coding-standards/schemas
- name: Upload GHAS Query Pack
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: code-scanning-cpp-query-pack.zip
path: code-scanning-cpp-query-pack.zip
+
+ - name: Create qlpack bundles
+ env:
+ CODEQL_HOME: ${{ github.workspace }}/codeql_home
+ run: |
+ PATH=$PATH:$CODEQL_HOME/codeql
+
+ codeql pack bundle --output=common-cpp-coding-standards.tgz cpp/common/src
+ codeql pack bundle --output=common-c-coding-standards.tgz c/common/src
+ codeql pack bundle --output=misra-c-coding-standards.tgz c/misra/src
+ codeql pack bundle --output=cert-c-coding-standards.tgz c/cert/src
+ codeql pack bundle --output=cert-cpp-coding-standards.tgz cpp/cert/src
+ codeql pack bundle --output=autosar-cpp-coding-standards.tgz cpp/autosar/src
+ codeql pack bundle --output=misra-cpp-coding-standards.tgz cpp/misra/src
+ codeql pack bundle --output=report-coding-standards.tgz cpp/report/src
+
+ - name: Upload qlpack bundles
+ uses: actions/upload-artifact@v4
+ with:
+ name: coding-standards-codeql-packs
+ path: '*-coding-standards.tgz'
\ No newline at end of file
diff --git a/.github/workflows/codeql_unit_tests.yml b/.github/workflows/codeql_unit_tests.yml
index 1a2374d19d..2fc28fc900 100644
--- a/.github/workflows/codeql_unit_tests.yml
+++ b/.github/workflows/codeql_unit_tests.yml
@@ -1,34 +1,38 @@
name: CodeQL Unit Testing
on:
+ merge_group:
+ types: [checks_requested]
push:
branches:
- main
- - "rc/**"
- next
+ - "rc/**"
pull_request:
branches:
- - "**"
- workflow_dispatch:
+ - main
+ - next
+ - "rc/**"
jobs:
+
prepare-unit-test-matrix:
name: Prepare CodeQL unit test matrix
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
outputs:
matrix: ${{ steps.export-unit-test-matrix.outputs.matrix }}
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Export unit test matrix
id: export-unit-test-matrix
run: |
echo "Merging Result:"
python scripts/create_language_matrix.py
- echo "::set-output name=matrix::$(
+ echo "matrix=$(
python scripts/create_language_matrix.py | \
- jq --compact-output 'map([.+{os: "ubuntu-20.04-xl", codeql_standard_library_ident : .codeql_standard_library | sub("\/"; "_")}]) | flatten | {include: .}')"
+ jq --compact-output 'map([.+{os: "ubuntu-latest-xl", codeql_standard_library_ident : .codeql_standard_library | sub("\/"; "_")}]) | flatten | {include: .}')" >> $GITHUB_OUTPUT
run-test-suites:
name: Run unit tests
@@ -38,19 +42,22 @@ jobs:
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.prepare-unit-test-matrix.outputs.matrix) }}
-
+
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Install Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: "3.9"
+ - name: Install Python dependencies
+ run: pip install -r scripts/requirements.txt
+
- name: Cache CodeQL
id: cache-codeql
- uses: actions/cache@v2.1.3
+ uses: actions/cache@v4
with:
# A list of files, directories, and wildcard patterns to cache and restore
path: ${{github.workspace}}/codeql_home
@@ -66,11 +73,15 @@ jobs:
codeql-home: ${{ github.workspace }}/codeql_home
add-to-path: false
+ - name: Install CodeQL packs
+ uses: ./.github/actions/install-codeql-packs
+ with:
+ cli_path: ${{ github.workspace }}/codeql_home/codeql
+
- name: Pre-Compile Queries
id: pre-compile-queries
run: |
- ${{ github.workspace }}/codeql_home/codeql/codeql query compile --search-path cpp --threads 0 cpp
- ${{ github.workspace }}/codeql_home/codeql/codeql query compile --search-path c --search-path cpp --threads 0 c
+ ${{ github.workspace }}/codeql_home/codeql/codeql query compile --threads 0 ${{ matrix.language }}
- name: Run test suites
@@ -93,7 +104,7 @@ jobs:
def print_error(fmt, *args):
print(f"::error::{fmt}", *args)
-
+
def print_error_and_fail(fmt, *args):
print_error(fmt, args)
sys.exit(1)
@@ -122,18 +133,11 @@ jobs:
os.makedirs(os.path.dirname(test_report_path), exist_ok=True)
test_report_file = open(test_report_path, 'w')
files_to_close.append(test_report_file)
- if "${{ matrix.language }}".casefold() == "c".casefold():
- # c tests require cpp -- but we don't want c things on the cpp
- # path in case of design errors.
- cpp_language_root = Path(workspace, 'cpp')
- procs.append(subprocess.Popen([codeql_bin, "test", "run", "--failing-exitcode=122", f"--slice={slice}/{num_slices}", "--ram=2048", "--format=json", f'--search-path={cpp_language_root}', f'--search-path={language_root}', *test_roots], stdout=test_report_file, stderr=subprocess.PIPE))
- else:
- procs.append(subprocess.Popen([codeql_bin, "test", "run", "--failing-exitcode=122", f"--slice={slice}/{num_slices}", "--ram=2048", "--format=json", f'--search-path={language_root}', f'--search-path={language_root}', *test_roots], stdout=test_report_file, stderr=subprocess.PIPE))
+ procs.append(subprocess.Popen([codeql_bin, "test", "run", "--failing-exitcode=122", f"--slice={slice}/{num_slices}", "--ram=2048", "--format=json", *test_roots], stdout=test_report_file, stderr=subprocess.PIPE))
for p in procs:
- p.wait()
+ _, err = p.communicate()
if p.returncode != 0:
- _, err = p.communicate()
if p.returncode == 122:
# Failed because a test case failed, so just print the regular output.
# This will allow us to proceed to validate-test-results, which will fail if
@@ -147,7 +151,7 @@ jobs:
file.close()
- name: Upload test results
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: ${{ matrix.language }}-test-results-${{ runner.os }}-${{ matrix.codeql_cli }}-${{ matrix.codeql_standard_library_ident }}
path: |
@@ -156,11 +160,18 @@ jobs:
validate-test-results:
name: Validate test results
- needs: [run-test-suites]
- runs-on: ubuntu-latest
+ if: ${{ always() }}
+ needs: run-test-suites
+ runs-on: ubuntu-22.04
steps:
+ - name: Check if run-test-suites job failed to complete, if so fail
+ if: ${{ needs.run-test-suites.result == 'failure' }}
+ uses: actions/github-script@v7
+ with:
+ script: |
+ core.setFailed('Test run job failed')
- name: Collect test results
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v4
- name: Validate test results
run: |
diff --git a/.github/workflows/create-draft-release.yml b/.github/workflows/create-draft-release.yml
deleted file mode 100644
index 43bf8cac06..0000000000
--- a/.github/workflows/create-draft-release.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Create draft release
-
-on:
- workflow_dispatch:
- inputs:
- release_version_tag:
- description: |
- The tag for the new draft release, e.g. v0.5.1.
- required: true
- codeql_analysis_threads:
- description: |
- Number of threads to evaluate queries
- required: true
- default: 6
- aws_ec2_instance_type:
- description: |
- Recommended specs: 8+ vCPU 32+ GB RAM (e.g. t2.2xlarge, r5.2xlarge)
- required: true
- default: r5.2xlarge
-
-jobs:
- create-draft-release:
- name: Create draft release
- runs-on: ubuntu-latest
- env:
- # AWS CONFIGURATION
- AWS_EC2_INSTANCE_TYPE: ${{ github.event.inputs.aws_ec2_instance_type }}
-
- # CODEQL CONFIGURATION
- CODEQL_ANALYSIS_THREADS: ${{ github.event.inputs.codeql_analysis_threads }}
-
- # INTEGRATION TESTING CONFIGURATION
- INTEGRATION_TESTING_ACCESS_TOKEN: ${{ secrets.INTEGRATION_TESTING_ACCESS_TOKEN }}
- WORKFLOW_ID: 11846210
-
- # RELEASE VERSION TAG
- RELEASE_VERSION_TAG: ${{ github.event.inputs.release_version_tag }}
- steps:
- - name: Checkout
- uses: actions/checkout@v2
- with:
- fetch-depth: 0
-
- - name: Install Python
- uses: actions/setup-python@v4
- with:
- python-version: "3.9"
-
- - name: Install generate_release_notes.py dependencies
- run: pip install -r scripts/requirements.txt
-
- - name: Create draft release
- run: |
- scripts/release/create_draft_release.sh ${GITHUB_REF#refs/heads/} "$RELEASE_VERSION_TAG"
- env:
- GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/dispatch-matrix-test-on-comment.yml b/.github/workflows/dispatch-matrix-test-on-comment.yml
new file mode 100644
index 0000000000..972cabdb89
--- /dev/null
+++ b/.github/workflows/dispatch-matrix-test-on-comment.yml
@@ -0,0 +1,52 @@
+name: 🤖 Run Matrix Check (On Comment)
+
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ dispatch-matrix-check:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Check permission
+ id: check-write-permission
+ uses: ./.github/actions/check-permissions
+ with:
+ minimum-permission: "write"
+
+ - name: Generate token
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.AUTOMATION_APP_ID }}
+ private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: "codeql-coding-standards-release-engineering"
+
+ - name: Invoke matrix testing job
+ if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/test-matrix') && steps.check-write-permission.outputs.has-permission }}
+ env:
+ ISSUE_NR: ${{ github.event.issue.number }}
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ run: |
+ jq -n \
+ --arg issue_nr "$ISSUE_NR" \
+ '{"issue-nr": $issue_nr}' \
+ | \
+ gh workflow run pr-compiler-validation.yml \
+ --json \
+ -R github/codeql-coding-standards-release-engineering
+
+ - uses: actions/github-script@v7
+ if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/test-matrix') && steps.check-write-permission.outputs.has-permission }}
+ with:
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: '🤖 Beep Boop! Matrix Testing for this PR has been initiated. Please check back later for results.
:bulb: If you do not hear back from me please check my status! **I will report even if this PR does not contain files eligible for matrix testing.**'
+ })
diff --git a/.github/workflows/dispatch-release-performance-check.yml b/.github/workflows/dispatch-release-performance-check.yml
new file mode 100644
index 0000000000..5886bb2ea8
--- /dev/null
+++ b/.github/workflows/dispatch-release-performance-check.yml
@@ -0,0 +1,52 @@
+name: 🏁 Run Release Performance Check
+
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ dispatch-matrix-check:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Check permission
+ id: check-write-permission
+ uses: ./.github/actions/check-permissions
+ with:
+ minimum-permission: "write"
+
+ - name: Generate token
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.AUTOMATION_APP_ID }}
+ private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: "codeql-coding-standards-release-engineering"
+
+ - name: Invoke performance test
+ if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/test-performance') && steps.check-write-permission.outputs.has-permission }}
+ env:
+ ISSUE_NR: ${{ github.event.issue.number }}
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ run: |
+ jq -n \
+ --arg issue_nr "$ISSUE_NR" \
+ '{"issue-nr": $issue_nr}' \
+ | \
+ gh workflow run pr-performance-testing.yml \
+ --json \
+ -R github/codeql-coding-standards-release-engineering
+
+ - uses: actions/github-script@v7
+ if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/test-performance') && steps.check-write-permission.outputs.has-permission }}
+ with:
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: '🏁 Beep Boop! Performance testing for this PR has been initiated. Please check back later for results. Note that the query package generation step must complete before testing will start so it might be a minute.
:bulb: If you do not hear back from me please check my status! **I will report even if I fail!**'
+ })
diff --git a/.github/workflows/extra-rule-validation.yml b/.github/workflows/extra-rule-validation.yml
new file mode 100644
index 0000000000..02d37f92b2
--- /dev/null
+++ b/.github/workflows/extra-rule-validation.yml
@@ -0,0 +1,60 @@
+name: ⚙️ Extra Rule Validation
+
+on:
+ merge_group:
+ types: [checks_requested]
+ push:
+ branches:
+ - main
+ - "rc/**"
+ - next
+ pull_request:
+ branches:
+ - main
+ - "rc/**"
+ - next
+
+
+jobs:
+ validate-rules-csv:
+ name: Validate Rules CSV
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Check Rules
+ shell: pwsh
+ run: scripts/util/Get-DuplicateRules.ps1 -Language 'all' -CIMode
+
+
+ validate-shared-rules-test-structure:
+ name: Validate Rules Test Structure
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Ensure CPP Shared Rules Have Valid Structure
+ shell: pwsh
+ run: scripts/util/Test-SharedImplementationsHaveTestCases.ps1 -Language cpp -CIMode
+
+ - name: Ensure C Shared Rules Have Valid Structure
+ shell: pwsh
+ run: scripts/util/Test-SharedImplementationsHaveTestCases.ps1 -Language c -CIMode
+
+
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: missing-test-report.csv
+ path: MissingTestReport*.csv
+
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: test-report.csv
+ path: TestReport*.csv
+ if-no-files-found: error
+
+
diff --git a/.github/workflows/finalize-release.yml b/.github/workflows/finalize-release.yml
new file mode 100644
index 0000000000..b3a96f32d3
--- /dev/null
+++ b/.github/workflows/finalize-release.yml
@@ -0,0 +1,133 @@
+name: Finalize Release
+on:
+ pull_request:
+ types:
+ - closed
+ branches:
+ - "rc/**"
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: |
+ The ref of release to finalize (e.g., 'rc/MAJOR.MINOR.PATCH').
+ required: true
+ tool-ref:
+ description: |
+ The ref to the tooling to use for the finalize (e.g., 'rc/MAJOR.MINOR.PATCH').
+ required: false
+
+jobs:
+ finalize-release:
+ if: (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || github.event_name == 'workflow_dispatch'
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Determine ref
+ env:
+ REF_FROM_INPUT: ${{ inputs.ref }}
+ TOOL_REF_FROM_INPUT: ${{ inputs.tool-ref }}
+ REF_FROM_PR: ${{ github.event.pull_request.merge_commit_sha }}
+ BASE_REF_FROM_PR: ${{ github.event.pull_request.base.ref }}
+ run: |
+ if [[ $GITHUB_EVENT_NAME == "workflow_dispatch" ]]; then
+ echo "REF=$REF_FROM_INPUT" >> "$GITHUB_ENV"
+ echo "TOOL_REF=$TOOL_REF_FROM_INPUT" >> "$GITHUB_ENV"
+ echo "BASE_REF=$REF_FROM_INPUT" >> "$GITHUB_ENV"
+ else
+ echo "REF=$REF_FROM_PR" >> "$GITHUB_ENV"
+ echo "TOOL_REF=$REF_FROM_PR" >> "$GITHUB_ENV"
+ echo "BASE_REF=$BASE_REF_FROM_PR" >> "$GITHUB_ENV"
+ fi
+
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ env.REF }}
+ fetch-depth: 0
+ path: release
+
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ env.TOOL_REF }}
+ path: tooling
+
+ - name: Install Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Install dependencies
+ run: pip install -r scripts/release/requirements.txt
+ working-directory: tooling
+
+ - name: Configure git
+ run: |
+ git config user.name "$GITHUB_ACTOR"
+ git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
+ working-directory: release
+
+ - name: Update release tag
+ run: |
+ version=${BASE_REF#rc/}
+ echo "Creating release tag v$version"
+
+ git tag -f -a v$version -m "Release v$version"
+ git push --force origin v$version
+ working-directory: release
+
+ - name: Finalize release
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ version=${BASE_REF#rc/}
+ echo "Finalizing release v$version"
+
+ gh release edit "v$version" --draft=false --tag=v$version
+ working-directory: release
+
+ - name: Determine if release was a hotfix release
+ run: |
+ version=${BASE_REF#rc/}
+ # We are running the script in the tooling directory with the release directory as the working directory
+ echo "HOTFIX_RELEASE=$(python ../tooling/scripts/release/is-hotfix-release.py $version)" >> "$GITHUB_ENV"
+ working-directory: release
+
+ - name: Determine next release version
+ if: env.HOTFIX_RELEASE == 'false'
+ run: |
+ version=${BASE_REF#rc/}
+ next_version=$(python scripts/release/next-version.py --component minor --pre-release dev -- $version)
+ echo "NEXT_VERSION=$next_version" >> "$GITHUB_ENV"
+ working-directory: tooling
+
+ - name: Generate token
+ if: env.HOTFIX_RELEASE == 'false'
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.AUTOMATION_APP_ID }}
+ private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: "codeql-coding-standards"
+
+ - name: Bump main version
+ if: env.HOTFIX_RELEASE == 'false'
+ env:
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ run: |
+ echo "Bumping main version to $NEXT_VERSION"
+
+ git switch main
+ git pull --ff-only origin main
+
+ git switch -c "release-automation/bump-version-to-$NEXT_VERSION"
+
+ # We are running the script in the tooling directory with the release directory as the working directory
+ ../tooling/scripts/release/bump-version.sh "$NEXT_VERSION"
+
+ git add -u .
+ git commit -m "Bump version to $NEXT_VERSION"
+ git push --set-upstream origin "release-automation/bump-version-to-$NEXT_VERSION"
+
+ gh pr create --repo $GITHUB_REPOSITORY --base main --head "release-automation/bump-version-to-$NEXT_VERSION" --body "Bump the version of main to $NEXT_VERSION" --title "Bump version to $NEXT_VERSION"
+ working-directory: release
diff --git a/.github/workflows/generate-html-docs.yml b/.github/workflows/generate-html-docs.yml
index 0142c2feed..d63f631421 100644
--- a/.github/workflows/generate-html-docs.yml
+++ b/.github/workflows/generate-html-docs.yml
@@ -1,6 +1,8 @@
name: Generate HTML documentation
on:
+ merge_group:
+ types: [checks_requested]
push:
branches:
- main
@@ -15,13 +17,13 @@ on:
jobs:
generate-html-doc:
name: Generate HTML documentation
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Install Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: "3.9"
@@ -33,7 +35,7 @@ jobs:
python scripts/documentation/generate_iso26262_docs.py coding-standards-html-docs
- name: Upload HTML documentation
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: coding-standards-docs-${{ github.sha }}
path: coding-standards-html-docs/
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
new file mode 100644
index 0000000000..75e297d42e
--- /dev/null
+++ b/.github/workflows/prepare-release.yml
@@ -0,0 +1,166 @@
+name: "Prepare CodeQL Coding Standards release"
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: |
+ The version to release (MUST follow semantic versioning so NO 'v' prefix).
+ required: true
+ ref:
+ description: |
+ The git commit, branch, or tag to release from.
+ required: true
+ hotfix:
+ description: |
+ Hotfix release.
+ required: false
+ default: false
+ type: boolean
+
+permissions:
+ contents: write
+ pull-requests: write
+ actions: write
+ checks: write
+
+env:
+ RELEASE_VERSION: ${{ inputs.version }}
+ HOTFIX_RELEASE: ${{ inputs.hotfix }}
+
+jobs:
+ prepare-release:
+ name: "Prepare release"
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref }}
+
+ - name: Install Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Install release script dependencies
+ run: pip install -r scripts/release/requirements.txt
+
+ - name: Validate version
+ run: |
+ if [[ "$HOTFIX_RELEASE" == "true" ]]; then
+ python scripts/release/validate-version.py --hotfix "$RELEASE_VERSION"
+ else
+ python scripts/release/validate-version.py "$RELEASE_VERSION"
+ fi
+
+ - name: Check if release exists
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ release=$( { gh release view "v$RELEASE_VERSION" --json name,isDraft; } || echo "" )
+ if [[ -z "$release" ]]; then
+ echo "Release v$RELEASE_VERSION does not exist. Proceeding"
+ echo "create_draft_release=true" >> "$GITHUB_ENV"
+ else
+ isDraft=$(echo "$release" | jq -r '.isDraft')
+ if [[ "$isDraft" != "true" ]]; then
+ echo "Release 'v$RELEASE_VERSION' already exists and is not a draft. Cannot proceed"
+ exit 1
+ else
+ echo "Release 'v$RELEASE_VERSION' already exists and is a draft. Proceeding"
+ echo "create_draft_release=false" >> "$GITHUB_ENV"
+ fi
+ fi
+
+ - name: Check if release PR exists
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ release_pr=$( { gh pr view "rc/$RELEASE_VERSION" --json title,state,number; } || echo "")
+ if [[ ! -z "$release_pr" ]]; then
+ pr_title=$(echo "$release_pr" | jq -r '.title')
+ pr_state=$(echo "$release_pr" | jq -r '.state')
+ pr_number=$(echo "$release_pr" | jq -r '.number')
+ echo "Found PR '$pr_title' with state '$pr_state'"
+ if [[ "$pr_title" == "Release v$RELEASE_VERSION" ]] && [[ "$pr_state" != "CLOSED" ]]; then
+ echo "Release PR is not closed, deleting it to proceed"
+ gh pr close --delete-branch $pr_number
+ fi
+ fi
+
+ - name: Delete existing release branch
+ run: |
+ if [[ ! -z $(git ls-remote --heads origin rc/$RELEASE_VERSION) ]]; then
+ echo "Deleting existing release branch"
+ git push origin --delete rc/$RELEASE_VERSION
+ fi
+
+ - name: Delete existing feature branch
+ run: |
+ if [[ ! -z $(git ls-remote --heads origin feature/update-user-manual-for-$RELEASE_VERSION) ]]; then
+ echo "Deleting existing feature branch"
+ git push origin --delete feature/update-user-manual-for-$RELEASE_VERSION
+ fi
+
+ - name: Configure git
+ run: |
+ git config user.name "$GITHUB_ACTOR"
+ git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
+
+ - name: Create release branch
+ run: |
+ git switch -c rc/$RELEASE_VERSION
+ git push --set-upstream origin rc/$RELEASE_VERSION
+
+ - name: Create draft release
+ if: env.create_draft_release == 'true'
+ env:
+ RELEASE_VERSION: ${{ inputs.version }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ # Create lightweight tag to reference release
+ git tag v$RELEASE_VERSION
+ git push -f origin v$RELEASE_VERSION
+
+ gh release create \
+ -R $GITHUB_REPOSITORY \
+ --title "v$RELEASE_VERSION" \
+ --draft \
+ --target rc/$RELEASE_VERSION \
+ v$RELEASE_VERSION
+
+ - name: Create feature branch for PR
+ run: |
+ git switch -c feature/update-user-manual-for-$RELEASE_VERSION
+ git push --set-upstream origin feature/update-user-manual-for-$RELEASE_VERSION
+
+ scripts/release/bump-version.sh "$RELEASE_VERSION"
+
+ git add -u .
+ git commit -m "Update version"
+ git push
+
+ - name: Generate token
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.AUTOMATION_APP_ID }}
+ private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: "codeql-coding-standards"
+
+ - name: Create release PR
+ env:
+ # Use the token from the `generate-token` step because we can't use the default workflow token
+ # to create a PR and generate PR events to trigger the next workflow because of recursive workflow
+ # trigger protection.
+ GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
+ run: |
+ gh pr create \
+ -R $GITHUB_REPOSITORY \
+ --title "Release v$RELEASE_VERSION" \
+ --body "This PR releases codeql-coding-standards version $RELEASE_VERSION." \
+ --base rc/$RELEASE_VERSION \
+ --head feature/update-user-manual-for-$RELEASE_VERSION \
+ --draft
diff --git a/.github/workflows/standard_library_upgrade_tests.yml b/.github/workflows/standard_library_upgrade_tests.yml
index 0a4e58dbd3..a401150b07 100644
--- a/.github/workflows/standard_library_upgrade_tests.yml
+++ b/.github/workflows/standard_library_upgrade_tests.yml
@@ -14,12 +14,12 @@ on:
jobs:
prepare-unit-test-matrix:
name: Prepare CodeQL unit test matrix
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
outputs:
matrix: ${{ steps.export-unit-test-matrix.outputs.matrix }}
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Export unit test matrix
id: export-unit-test-matrix
@@ -41,16 +41,16 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup Python 3
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Cache CodeQL
id: cache-codeql
- uses: actions/cache@v2.1.3
+ uses: actions/cache@v4
with:
# A list of files, directories, and wildcard patterns to cache and restore
path: ${{github.workspace}}/codeql_home
@@ -116,7 +116,7 @@ jobs:
stdlib_path = os.path.join(codeql_home, 'codeql-stdlib')
cpp_test_root = Path(stdlib_path, 'cpp/ql/test')
print(f"Executing tests found (recursively) in the directory '{cpp_test_root}'")
- cp = subprocess.run([codeql_bin, "test", "run", "--format=json", f'--search-path={stdlib_path}', cpp_test_root], stdout=test_report_file, stderr=subprocess.PIPE)
+ cp = subprocess.run([codeql_bin, "test", "run", "--format=json", cpp_test_root], stdout=test_report_file, stderr=subprocess.PIPE)
if cp.returncode != 0:
print_error_and_fail(f"Failed to run tests with return code {cp.returncode} and error {cp.stderr}")
@@ -143,7 +143,7 @@ jobs:
}, test_summary_file)
- name: Upload test results
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
with:
name: test-results-${{runner.os}}-${{matrix.codeql_cli}}-${{matrix.codeql_standard_library_ident}}
path: |
@@ -154,15 +154,15 @@ jobs:
validate-test-results:
name: Validate test results
needs: [run-test-suites]
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
steps:
- name: Install Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Collect test results
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v4
- name: Validate test results
shell: python
diff --git a/.github/workflows/tooling-unit-tests.yml b/.github/workflows/tooling-unit-tests.yml
new file mode 100644
index 0000000000..3f4fde932d
--- /dev/null
+++ b/.github/workflows/tooling-unit-tests.yml
@@ -0,0 +1,117 @@
+name: 🧰 Tooling unit tests
+
+on:
+ merge_group:
+ types: [checks_requested]
+ push:
+ branches:
+ - main
+ - "rc/**"
+ - next
+ pull_request:
+ branches:
+ - main
+ - "rc/**"
+ - next
+
+jobs:
+ prepare-supported-codeql-env-matrix:
+ name: Prepare supported CodeQL environment matrix
+ runs-on: ubuntu-22.04
+ outputs:
+ matrix: ${{ steps.export-supported-codeql-env-matrix.outputs.matrix }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Export supported CodeQL environment matrix
+ id: export-supported-codeql-env-matrix
+ run: |
+ echo "::set-output name=matrix::$(
+ jq --compact-output '.supported_environment | {include: .}' supported_codeql_configs.json
+ )"
+
+ analysis-report-tests:
+ name: Run analysis report tests
+ needs: prepare-supported-codeql-env-matrix
+ runs-on: ubuntu-22.04
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJSON(needs.prepare-supported-codeql-env-matrix.outputs.matrix) }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Install Python dependencies
+ run: pip install -r scripts/reports/requirements.txt
+
+ - name: Cache CodeQL
+ id: cache-codeql
+ uses: actions/cache@v4
+ with:
+ path: ${{ github.workspace }}/codeql_home
+ key: codeql-home-${{ matrix.os }}-${{ matrix.codeql_cli }}-${{ matrix.codeql_standard_library }}
+
+ - name: Install CodeQL
+ if: steps.cache-codeql.outputs.cache-hit != 'true'
+ uses: ./.github/actions/install-codeql
+ with:
+ codeql-cli-version: ${{ matrix.codeql_cli }}
+ codeql-stdlib-version: ${{ matrix.codeql_standard_library }}
+ codeql-home: ${{ github.workspace }}/codeql_home
+ add-to-path: false
+
+ - name: Install CodeQL packs
+ uses: ./.github/actions/install-codeql-packs
+ with:
+ cli_path: ${{ github.workspace }}/codeql_home/codeql
+
+ - name: Run PyTest
+ env:
+ CODEQL_HOME: ${{ github.workspace }}/codeql_home
+ run: |
+ PATH=$PATH:$CODEQL_HOME/codeql
+ pytest scripts/reports/analysis_report_test.py
+
+ recategorization-tests:
+ name: Run Guideline Recategorization tests
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Install Python dependencies
+ run: pip install -r scripts/guideline_recategorization/requirements.txt
+
+ - name: Run PyTest
+ run: |
+ pytest scripts/guideline_recategorization/recategorize_test.py
+
+ release-tests:
+ name: Run release tests
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Install Python dependencies
+ run: pip install -r scripts/release/requirements.txt
+
+ - name: Run PyTest
+ run: |
+ pytest scripts/release/update_release_assets_test.py
\ No newline at end of file
diff --git a/.github/workflows/update-check-run.yml b/.github/workflows/update-check-run.yml
new file mode 100644
index 0000000000..225c81fa24
--- /dev/null
+++ b/.github/workflows/update-check-run.yml
@@ -0,0 +1,70 @@
+name: Update check run
+
+on:
+ workflow_dispatch:
+ inputs:
+ id:
+ description: |
+ The unique identifier of the check run.
+ type: number
+ required: true
+ status:
+ description: |
+ The current status.
+
+ Can be one of: queued, in_progress, completed
+ type: string
+ required: true
+ conclusion:
+ description: |
+ The final conclusion of the check when completed.
+
+ Can be one of: action_required, cancelled, failure, neutral, success, skipped, stale, timed_out
+ type: string
+ details_url:
+ description: |
+ The URL of the integrator's site that has the full details of the check.
+ type: string
+ external_id:
+ description: |
+ A reference for the run on the integrator's system.
+ type: string
+ output:
+ description: |
+ The output object for the check run.
+
+ See https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28#update-a-check-run for more information.
+ type: string
+ default: '{}'
+
+permissions:
+ checks: write
+
+jobs:
+ update-check-run:
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Update check run
+ env:
+ CHECK_RUN_ID: ${{ inputs.id }}
+ CHECK_RUN_STATUS: ${{ inputs.status }}
+ CHECK_RUN_CONCLUSION: ${{ inputs.conclusion }}
+ CHECK_RUN_DETAILS_URL: ${{ inputs.details_url }}
+ CHECK_RUN_EXTERNAL_ID: ${{ inputs.external_id }}
+ CHECK_RUN_OUTPUT: ${{ inputs.output }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ jq -n \
+ --arg status "$CHECK_RUN_STATUS" \
+ --arg conclusion "$CHECK_RUN_CONCLUSION" \
+ --arg details_url "$CHECK_RUN_DETAILS_URL" \
+ --arg external_id "$CHECK_RUN_EXTERNAL_ID" \
+ --argjson output "$CHECK_RUN_OUTPUT" \
+ '{status: $status, conclusion: $conclusion, details_url: $details_url, external_id: $external_id, output: $output} | to_entries | map(select(.value != "" and .value != {})) | from_entries' \
+ | \
+ gh api \
+ --method PATCH \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --input - \
+ /repos/$GITHUB_REPOSITORY/check-runs/$CHECK_RUN_ID
diff --git a/.github/workflows/update-release-status.yml b/.github/workflows/update-release-status.yml
new file mode 100644
index 0000000000..874bc4d0b2
--- /dev/null
+++ b/.github/workflows/update-release-status.yml
@@ -0,0 +1,138 @@
+name: "Update Release Status"
+on:
+ workflow_dispatch:
+ inputs:
+ head-sha:
+ description: |
+ The head SHA to use.
+ type: string
+ required: true
+
+permissions:
+ actions: write
+ checks: write
+ contents: write
+
+env:
+ HEAD_SHA: ${{ inputs.head-sha }}
+
+jobs:
+ validate-check-runs:
+ runs-on: ubuntu-22.04
+ outputs:
+ status: ${{ steps.set-output.outputs.status }}
+ conclusion: ${{ steps.set-output.outputs.conclusion }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.head-sha }}
+
+ - name: Get release status check run
+ id: get-check-run
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ check_run_info=$(gh api \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --jq '.check_runs[] | select(.name == "release-status") | {id: .id, status: .status, conclusion: .conclusion}' \
+ /repos/$GITHUB_REPOSITORY/commits/$HEAD_SHA/check-runs)
+
+ if [[ -z "$check_run_info" ]]; then
+ echo "No release status check run found"
+ exit 1
+ fi
+
+ check_run_id=$(echo "$check_run_info" | jq -r '.id')
+ check_run_status=$(echo "$check_run_info" | jq -r '.status')
+ check_run_conclusion=$(echo "$check_run_info" | jq -r '.conclusion')
+
+ echo "CHECK_RUN_ID=$check_run_id" >> "$GITHUB_ENV"
+ echo "CHECK_RUN_STATUS=$check_run_status" >> "$GITHUB_ENV"
+ echo "CHECK_RUN_CONCLUSION=$check_run_conclusion" >> "$GITHUB_ENV"
+
+ - name: Reset release status
+ if: env.CHECK_RUN_STATUS == 'completed'
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ check_run_id=$(gh api \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --field name="release-status" \
+ --field head_sha="$HEAD_SHA" \
+ --jq ".id" \
+ /repos/$GITHUB_REPOSITORY/check-runs)
+
+ echo "Created release status check run with id $check_run_id"
+ # Reset the status to in progress.
+ echo "CHECK_RUN_STATUS=in_progress" >> "$GITHUB_ENV"
+ echo "CHECK_RUN_ID=$check_run_id" >> "$GITHUB_ENV"
+
+ - name: Check all runs completed
+ if: env.CHECK_RUN_STATUS != 'completed'
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ check_runs=$(gh api \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --jq '.check_runs | map(select(.name != "release-status"))' \
+ /repos/$GITHUB_REPOSITORY/commits/$HEAD_SHA/check-runs)
+
+ status_stats=$(echo "$check_runs" | jq -r '. | {failed: (map(select(.conclusion == "failure")) | length), pending: (map(select(.status != "completed")) | length) }')
+
+ echo "status_stats=$status_stats"
+
+ failed=$(echo "$status_stats" | jq -r '.failed')
+ pending=$(echo "$status_stats" | jq -r '.pending')
+
+ echo "CHECK_RUNS_FAILED=$failed" >> "$GITHUB_ENV"
+ echo "CHECK_RUNS_PENDING=$pending" >> "$GITHUB_ENV"
+
+ - name: Conclude release status
+ if: env.CHECK_RUNS_PENDING == '0' && env.CHECK_RUN_STATUS != 'completed'
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ if [[ "$CHECK_RUNS_FAILED" == "0" ]]; then
+ echo "All check runs succeeded"
+ conclusion="success"
+ else
+ echo "Some check runs failed"
+ conclusion="failure"
+ fi
+
+ jq -n \
+ --arg status "completed" \
+ --arg conclusion "$conclusion" \
+ '{status: $status, conclusion: $conclusion}' \
+ | \
+ gh api \
+ --method PATCH \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --input - \
+ /repos/$GITHUB_REPOSITORY/check-runs/$CHECK_RUN_ID
+
+ echo "RELEASE_STATUS_CONCLUSION=$conclusion" >> "$GITHUB_ENV"
+
+ - name: Set output
+ id: set-output
+ run: |
+ echo "conclusion=$RELEASE_STATUS_CONCLUSION" >> "$GITHUB_OUTPUT"
+ if [[ "$CHECK_RUNS_PENDING" == "0" ]]; then
+ echo "status=completed" >> "$GITHUB_OUTPUT"
+ else
+ echo "status=in_progress" >> "$GITHUB_OUTPUT"
+ fi
+
+ update-release:
+ needs: validate-check-runs
+ if: needs.validate-check-runs.outputs.status == 'completed' && needs.validate-check-runs.outputs.conclusion == 'success'
+ uses: ./.github/workflows/update-release.yml
+ with:
+ head-sha: ${{ inputs.head-sha }}
+ secrets:
+ AUTOMATION_PRIVATE_KEY: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
diff --git a/.github/workflows/update-release.yml b/.github/workflows/update-release.yml
new file mode 100644
index 0000000000..e3b8045514
--- /dev/null
+++ b/.github/workflows/update-release.yml
@@ -0,0 +1,72 @@
+name: Update Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ head-sha:
+ description: |
+ The head SHA of the release PR to use for finalizing the release.
+ required: true
+ workflow_call:
+ inputs:
+ head-sha:
+ type: string
+ description: |
+ The head SHA of the release PR to use for finalizing the release.
+ required: true
+ secrets:
+ AUTOMATION_PRIVATE_KEY:
+ description: |
+ The private key to use to generate a token for accessing the release engineering repository.
+ required: true
+env:
+ HEAD_SHA: ${{ inputs.head-sha }}
+
+jobs:
+ update-release:
+ name: "Update release"
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # We need the full history to compute the changelog
+ ref: ${{ inputs.head-sha }}
+
+ - name: Install Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Install dependencies
+ run: pip install -r scripts/release/requirements.txt
+
+ - name: Generate token
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.AUTOMATION_APP_ID }}
+ private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: "codeql-coding-standards-release-engineering"
+
+ - name: Update release assets
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ RELEASE_ENGINEERING_TOKEN: ${{ steps.generate-token.outputs.token }}
+ run: |
+ python scripts/release/update_release_assets.py \
+ --head-sha $HEAD_SHA \
+ --layout scripts/release/release-layout.yml \
+ --repo "$GITHUB_REPOSITORY" \
+ --github-token "$GITHUB_REPOSITORY:$GITHUB_TOKEN" "github/codeql-coding-standards-release-engineering:$RELEASE_ENGINEERING_TOKEN" \
+ --skip-checkrun "release-status" "Update Release"
+
+ - name: Update release notes
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ python scripts/release/update-release-notes.py \
+ --head-sha $HEAD_SHA \
+ --repo "$GITHUB_REPOSITORY" \
+ --github-token "$GITHUB_TOKEN"
diff --git a/.github/workflows/upgrade_codeql_dependencies.yml b/.github/workflows/upgrade_codeql_dependencies.yml
index 01f8bcf339..1ffc874bb4 100644
--- a/.github/workflows/upgrade_codeql_dependencies.yml
+++ b/.github/workflows/upgrade_codeql_dependencies.yml
@@ -7,51 +7,65 @@ on:
description: |
The version of the CodeQL CLI to be set as the default.
required: true
- codeql_standard_library_commit:
- description: |
- The tag or commit to use from the CodeQL Standard Library
- required: true
env:
XARGS_MAX_PROCS: 4
jobs:
- say_hello:
+ upgrade_codeql_dependencies:
env:
CODEQL_CLI_VERSION: ${{ github.event.inputs.codeql_cli_version }}
- CODEQL_LIB_COMMIT: ${{ github.event.inputs.codeql_standard_library_commit }}
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
steps:
- name: Checkout
- uses: actions/checkout@v2
-
- - name: Update the supported environment
- run: |
- jq \
- --arg cli_version "$CODEQL_CLI_VERSION" \
- --arg standard_library_commit "$CODEQL_LIB_COMMIT" \
- --raw-output \
- '.supported_environment | .[0] | .codeql_cli = $cli_version | .codeql_standard_library = $standard_library_commit' \
- supported_codeql_configs.json
+ uses: actions/checkout@v4
- name: Fetch CodeQL
env:
GITHUB_TOKEN: ${{ github.token }}
+ RUNNER_TEMP: ${{ runner.temp }}
run: |
+ cd $RUNNER_TEMP
gh release download "v${CODEQL_CLI_VERSION}" --repo https://github.com/github/codeql-cli-binaries --pattern codeql-linux64.zip
unzip -q codeql-linux64.zip
+ echo "$RUNNER_TEMP/codeql/" >> $GITHUB_PATH
+
+ - name: Install Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Install upgrade-codeql-dependencies.py dependencies
+ run: pip install -r scripts/upgrade-codeql-dependencies/requirements.txt
+
+ - name: Update the supported environment
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ CODEQL_CLI_VERSION: ${{ github.event.inputs.codeql_cli_version }}
+ run: |
+ python3 scripts/upgrade-codeql-dependencies/upgrade-codeql-dependencies.py --cli-version "$CODEQL_CLI_VERSION"
- name: Update CodeQL formatting based on new CLI version
+ env:
+ RUNNER_TEMP: ${{ runner.temp }}
run: |
- find cpp -name '*.ql' -or -name '*.qll' | xargs --max-procs "$XARGS_MAX_PROCS" --max-args 1 codeql/codeql query format --in-place
- find c -name '*.ql' -or -name '*.qll' | xargs --max-procs "$XARGS_MAX_PROCS" --max-args 1 codeql/codeql query format --in-place
+ find cpp \( -name '*.ql' -or -name '*.qll' \) -print0 | xargs -0 --max-procs "$XARGS_MAX_PROCS" codeql query format --in-place
+ find c \( -name '*.ql' -or -name '*.qll' \) -print0 | xargs -0 --max-procs "$XARGS_MAX_PROCS" codeql query format --in-place
- name: Create Pull Request
- uses: peter-evans/create-pull-request@v3
+ uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
- title: "Upgrading `github/codeql` dependency to ${{ github.event.inputs.codeql_standard_library_commit }}"
- body: "This PR upgrades the CodeQL CLI version to ${{ github.event.inputs.codeql_cli_version }} and the `github/codeql` version to ${{ github.event.inputs.codeql_standard_library_commit }}."
- commit-message: "Upgrading `github/codeql` dependency to ${{ github.event.inputs.codeql_standard_library_commit }}"
- team-reviewers: github/codeql-coding-standards
+ title: "Upgrade `github/codeql` dependency to ${{ github.event.inputs.codeql_cli_version }}"
+ body: |
+ This PR upgrades the CodeQL CLI version to ${{ github.event.inputs.codeql_cli_version }}.
+
+ ## CodeQL dependency upgrade checklist:
+
+ - [ ] Confirm the code has been correctly reformatted according to the new CodeQL CLI.
+ - [ ] Identify any CodeQL compiler warnings and errors, and update queries as required.
+ - [ ] Validate that the `github/codeql` test cases succeed.
+ - [ ] Address any CodeQL test failures in the `github/codeql-coding-standards` repository.
+ - [ ] Validate performance vs pre-upgrade, using /test-performance
+ commit-message: "Upgrading `github/codeql` dependency to ${{ github.event.inputs.codeql_cli_version }}"
delete-branch: true
- branch: "codeql/upgrade-to-${{ github.event.inputs.codeql_standard_library_commit }}-${{ github.event.inputs.codeql_cli_version }}"
+ branch: "codeql/upgrade-to-${{ github.event.inputs.codeql_cli_version }}"
diff --git a/.github/workflows/validate-coding-standards.yml b/.github/workflows/validate-coding-standards.yml
deleted file mode 100644
index f7fc7563a1..0000000000
--- a/.github/workflows/validate-coding-standards.yml
+++ /dev/null
@@ -1,174 +0,0 @@
-name: Validating Coding Standards
-
-on:
- push:
- branches:
- - main
- - "rc/**"
- - next
- pull_request:
- branches:
- - main
- - "rc/**"
- - next
-
-env:
- XARGS_MAX_PROCS: 4
-
-jobs:
- validate-package-files:
- name: Validate Package Files
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Install Python
- uses: actions/setup-python@v4
- with:
- python-version: "3.9"
-
- - name: Install generate_package_files.py dependencies
- run: pip install -r scripts/requirements.txt
-
- - name: Validate Package Descriptions (CPP)
- run: |
- python scripts/validate-rule-package.py rule_packages/cpp/*.json
-
- - name: Validate Package Descriptions (C)
- run: |
- python scripts/validate-rule-package.py rule_packages/c/*.json
-
- - name: Validate Package Descriptions consistency (CPP)
- run: |
- python scripts/verify_rule_package_consistency.py cpp
-
- - name: Validate Package Descriptions consistency (C)
- run: |
- python scripts/verify_rule_package_consistency.py c
-
- - name: Validate Package Files (CPP)
- run: |
- find rule_packages/cpp -name \*.json -exec basename {} .json \; | xargs --max-procs "$XARGS_MAX_PROCS" --max-args 1 python scripts/generate_rules/generate_package_files.py cpp
- git diff
- git diff --compact-summary
- git diff --quiet
-
- - name: Validate Package Files (C)
- run: |
- find rule_packages/c -name \*.json -exec basename {} .json \; | xargs --max-procs "$XARGS_MAX_PROCS" --max-args 1 python scripts/generate_rules/generate_package_files.py c
- git diff
- git diff --compact-summary
- git diff --quiet
-
- validate-codeql-format:
- name: "Validate CodeQL Format"
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Fetch CodeQL
- run: |
- TAG="v$( jq -r '.supported_environment | .[0] | .codeql_cli' supported_codeql_configs.json)"
- gh release download $TAG --repo https://github.com/github/codeql-cli-binaries --pattern codeql-linux64.zip
- unzip -q codeql-linux64.zip
- env:
- GITHUB_TOKEN: ${{ github.token }}
-
- - name: Validate CodeQL Format (CPP)
- run: |
- find cpp -name \*.ql -or -name \*.qll -print0 | xargs -0 --max-procs "$XARGS_MAX_PROCS" codeql/codeql query format --in-place
-
- git diff
- git diff --compact-summary
- git diff --quiet
-
- - name: Validate CodeQL Format (C)
- run: |
- find c -name \*.ql -or -name \*.qll -print0 | xargs -0 --max-procs "$XARGS_MAX_PROCS" codeql/codeql query format --in-place
-
- git diff
- git diff --compact-summary
- git diff --quiet
-
- validate-query-help-files:
- name: Validate Query Help Files
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Validate CPP Query Help Files
- run: |
- exit_code=0
- for help_file in `find cpp -name '*.md'`
- do
- if grep -F -q 'REPLACE THIS' "$help_file" > /dev/null
- then
- echo "Help file $help_file contains placeholders that are not replaced or removed!"
- exit_code=1
- fi
- done
-
- exit $exit_code
-
- - name: Validate C Query Help Files
- run: |
- exit_code=0
- for help_file in `find c -name '*.md'`
- do
- if grep -F -q 'REPLACE THIS' "$help_file" > /dev/null
- then
- echo "Help file $help_file contains placeholders that are not replaced or removed!"
- exit_code=1
- fi
- done
-
- exit $exit_code
-
- validate-cpp-test-files:
- name: Validate C++ Test Files
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Install clang-format
- run: |
- sudo apt-get install --yes --quiet --no-install-recommends clang-format
- echo "::debug::$(clang-format -version)"
-
- - name: Validate C++ Test Files
- run: |
- if ! test -f .clang-format; then
- echo "Cannot find .clang-format in '$PWD'. Exiting..."
- fi
-
- find cpp/*/test -name \*.cpp -print0 | xargs -0 --max-procs "$XARGS_MAX_PROCS" clang-format --style=file -i --verbose
- git diff
- git diff --compact-summary
- git diff --quiet
-
- validate-c-test-files:
- name: Validate C Test Files
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Install clang-format
- run: |
- sudo apt-get install --yes --quiet --no-install-recommends clang-format
- echo "::debug::$(clang-format -version)"
-
- - name: Validate C++ Test Files
- run: |
- if ! test -f .clang-format; then
- echo "Cannot find .clang-format in '$PWD'. Exiting..."
- fi
-
- find c/*/test -name \*.c -print0 | xargs -0 --max-procs "$XARGS_MAX_PROCS" clang-format --style=file -i --verbose
- git diff
- git diff --compact-summary
- git diff --quiet
diff --git a/.github/workflows/validate-package-files.yml b/.github/workflows/validate-package-files.yml
new file mode 100644
index 0000000000..a716921053
--- /dev/null
+++ b/.github/workflows/validate-package-files.yml
@@ -0,0 +1,65 @@
+name: Validate Package Files
+on:
+ merge_group:
+ types: [checks_requested]
+ pull_request:
+ branches:
+ - main
+ - next
+ - "rc/**"
+
+jobs:
+ validate-package-files:
+ strategy:
+ matrix:
+ language: [cpp, c]
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref }}
+
+ - name: Install Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Install CodeQL
+ run: |
+ VERSION="v$( jq -r '.supported_environment | .[0] | .codeql_cli' supported_codeql_configs.json)"
+ gh extensions install github/gh-codeql
+ gh codeql set-version "$VERSION"
+ gh codeql install-stub
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+
+ - name: Install generate_package_files.py dependencies
+ run: pip install -r scripts/requirements.txt
+
+ - name: Validate Package Descriptions
+ env:
+ LANGUAGE: ${{ matrix.language }}
+ run: |
+ python scripts/validate-rule-package.py rule_packages/$LANGUAGE/*.json
+
+ - name: Validate Package Descriptions consistency
+ env:
+ LANGUAGE: ${{ matrix.language }}
+ run: |
+ python scripts/verify_rule_package_consistency.py $LANGUAGE
+
+ - name: Validate Current versus Expected Package Files
+ env:
+ LANGUAGE: ${{ matrix.language }}
+ run: |
+ find rule_packages/$LANGUAGE -name \*.json -exec basename {} .json \; | xargs python scripts/generate_rules/generate_package_files.py $LANGUAGE
+ git diff
+ git diff --compact-summary
+ git diff --quiet
+
+ - name: Validate Amendments
+ env:
+ LANGUAGE: ${{ matrix.language }}
+ run: |
+ python scripts/validate-amendments-csv.py $LANGUAGE
\ No newline at end of file
diff --git a/.github/workflows/validate-query-formatting.yml b/.github/workflows/validate-query-formatting.yml
new file mode 100644
index 0000000000..88b4c0d438
--- /dev/null
+++ b/.github/workflows/validate-query-formatting.yml
@@ -0,0 +1,44 @@
+name: "Validate Query Formatting"
+on:
+ merge_group:
+ types: [checks_requested]
+ pull_request:
+ branches:
+ - main
+ - next
+ - "rc/**"
+
+env:
+ XARGS_MAX_PROCS: 4
+
+jobs:
+ validate-query-formatting:
+ strategy:
+ matrix:
+ language: [cpp, c]
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref }}
+
+ - name: Install CodeQL
+ run: |
+ VERSION="v$( jq -r '.supported_environment | .[0] | .codeql_cli' supported_codeql_configs.json)"
+ gh extensions install github/gh-codeql
+ gh codeql set-version "$VERSION"
+ gh codeql install-stub
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+
+ - name: Validate query format
+ env:
+ LANGUAGE: ${{ matrix.language }}
+ run: |
+ codeql version
+ find $LANGUAGE \( -name \*.ql -or -name \*.qll \) -print0 | xargs -0 --max-procs "$XARGS_MAX_PROCS" codeql query format --in-place
+
+ git diff
+ git diff --compact-summary
+ git diff --quiet
\ No newline at end of file
diff --git a/.github/workflows/validate-query-help.yml b/.github/workflows/validate-query-help.yml
new file mode 100644
index 0000000000..90f0d16dbc
--- /dev/null
+++ b/.github/workflows/validate-query-help.yml
@@ -0,0 +1,37 @@
+name: Validate Query Help Files
+on:
+ merge_group:
+ types: [checks_requested]
+ pull_request:
+ branches:
+ - main
+ - next
+ - "rc/**"
+
+jobs:
+ validate-query-help-files:
+ strategy:
+ matrix:
+ language: [cpp, c]
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref }}
+
+ - name: Validate Query Help Files
+ env:
+ LANGUAGE: ${{ matrix.language }}
+ run: |
+ exit_code=0
+ for help_file in `find $LANGUAGE -name '*.md'`
+ do
+ if grep -F -q 'REPLACE THIS' "$help_file" > /dev/null
+ then
+ echo "Help file $help_file contains placeholders that are not replaced or removed!"
+ exit_code=1
+ fi
+ done
+
+ exit $exit_code
\ No newline at end of file
diff --git a/.github/workflows/validate-query-test-case-formatting.yml b/.github/workflows/validate-query-test-case-formatting.yml
new file mode 100644
index 0000000000..879c1c9058
--- /dev/null
+++ b/.github/workflows/validate-query-test-case-formatting.yml
@@ -0,0 +1,45 @@
+name: Validate Query Test Case Formatting
+on:
+ merge_group:
+ types: [checks_requested]
+ pull_request:
+ branches:
+ - main
+ - next
+ - "rc/**"
+
+env:
+ XARGS_MAX_PROCS: 4
+
+jobs:
+ validate-test-case-files:
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ language: [cpp, c]
+ fail-fast: false
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref }}
+
+ - name: Install clang-format
+ run: |
+ sudo apt-get install --yes --quiet --no-install-recommends clang-format
+
+ - name: Validating Current versus Expected Test Case Formatting
+ env:
+ LANGUAGE: ${{ matrix.language }}
+ # IMPORTANT: This step current relies on the fact that a file extension is the same as the language name for simplicity.
+ run: |
+ if ! test -f .clang-format; then
+ echo "Cannot find .clang-format in '$PWD'. Exiting..."
+ fi
+
+ find $LANGUAGE/*/test -name \*.$LANGUAGE -print0 | xargs -0 --max-procs "$XARGS_MAX_PROCS" clang-format --style=file -i --verbose
+ git diff
+ git diff --compact-summary
+ git diff --quiet
+
+
\ No newline at end of file
diff --git a/.github/workflows/validate-release.yml b/.github/workflows/validate-release.yml
new file mode 100644
index 0000000000..cd7d27f6fa
--- /dev/null
+++ b/.github/workflows/validate-release.yml
@@ -0,0 +1,171 @@
+name: Validate release
+
+on:
+ pull_request:
+ branches:
+ - "rc/**"
+
+permissions:
+ contents: read
+ actions: write
+ checks: write
+
+env:
+ HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+
+jobs:
+ pre-validate-performance:
+ outputs:
+ check-run-id: ${{ steps.create-check-run.outputs.check-run-id }}
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Create check run
+ id: create-check-run
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ check_run_id=$(gh api \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --field name="performance-test" \
+ --field head_sha="$HEAD_SHA" \
+ --jq ".id" \
+ /repos/$GITHUB_REPOSITORY/check-runs)
+
+ echo "check-run-id=$check_run_id" >> "$GITHUB_OUTPUT"
+
+ validate-performance:
+ needs: pre-validate-performance
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Generate token
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.AUTOMATION_APP_ID }}
+ private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: "codeql-coding-standards-release-engineering"
+ - name: Invoke performance test
+ env:
+ CHECK_RUN_ID: ${{ needs.pre-validate-performance.outputs.check-run-id }}
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ run: |
+ jq -n \
+ --arg ref "$HEAD_SHA" \
+ --arg check_run_id "$CHECK_RUN_ID" \
+ '{ref: $ref, "check-run-id": $check_run_id}' \
+ | \
+ gh workflow run release-performance-testing.yml \
+ --json \
+ -R github/codeql-coding-standards-release-engineering
+
+ on-failure-validate-performance-dispatch:
+ needs: [pre-validate-performance, validate-performance]
+ if: failure()
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Fail check run status
+ env:
+ CHECK_RUN_ID: ${{ needs.pre-validate-performance.outputs.check-run-id }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ jq -n \
+ --arg status "completed" \
+ --arg conclusion "failure" \
+ '{status: $status, conclusion: $conclusion}' \
+ | \
+ gh api \
+ --method PATCH \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --input - \
+ /repos/$GITHUB_REPOSITORY/check-runs/$CHECK_RUN_ID
+
+ pre-validate-compiler-compatibility:
+ outputs:
+ check-run-id: ${{ steps.create-check-run.outputs.check-run-id }}
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Create check run
+ id: create-check-run
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ check_run_id=$(gh api \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --field name="compiler-compatibility-test" \
+ --field head_sha="$HEAD_SHA" \
+ --jq ".id" \
+ /repos/$GITHUB_REPOSITORY/check-runs)
+
+ echo "check-run-id=$check_run_id" >> "$GITHUB_OUTPUT"
+
+ validate-compiler-compatibility:
+ needs: pre-validate-compiler-compatibility
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Generate token
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.AUTOMATION_APP_ID }}
+ private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}
+ owner: ${{ github.repository_owner }}
+ repositories: "codeql-coding-standards-release-engineering"
+ - name: Invoke compiler compatibility test
+ env:
+ CHECK_RUN_ID: ${{ needs.pre-validate-compiler-compatibility.outputs.check-run-id }}
+ GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
+ run: |
+ jq -n \
+ --arg ref "$HEAD_SHA" \
+ --arg check_run_id "$CHECK_RUN_ID" \
+ '{ref: $ref, "check-run-id": $check_run_id}' \
+ | \
+ gh workflow run release-compiler-validation.yml \
+ --json \
+ -R github/codeql-coding-standards-release-engineering
+
+ on-failure-validate-compiler-compatibility-dispatch:
+ needs:
+ [pre-validate-compiler-compatibility, validate-compiler-compatibility]
+ if: failure()
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Fail check run status
+ env:
+ CHECK_RUN_ID: ${{ needs.pre-validate-compiler-compatibility.outputs.check-run-id }}
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ jq -n \
+ --arg status "completed" \
+ --arg conclusion "failure" \
+ '{status: $status, conclusion: $conclusion}' \
+ | \
+ gh api \
+ --method PATCH \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --input - \
+ /repos/$GITHUB_REPOSITORY/check-runs/$CHECK_RUN_ID
+
+ create-release-status-check-run:
+ name: "Initialize release status monitoring"
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Create release status check run
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ CHECK_RUN_ID=$(gh api \
+ --header "Accept: application/vnd.github+json" \
+ --header "X-GitHub-Api-Version: 2022-11-28" \
+ --field name="release-status" \
+ --field head_sha="$HEAD_SHA" \
+ --field status="in_progress" \
+ --jq ".id" \
+ /repos/$GITHUB_REPOSITORY/check-runs)
+
+ echo "Created release status check run with id $CHECK_RUN_ID for $GITHUB_SHA"
diff --git a/.github/workflows/verify-standard-library-dependencies.yml b/.github/workflows/verify-standard-library-dependencies.yml
new file mode 100644
index 0000000000..06ab4d23e2
--- /dev/null
+++ b/.github/workflows/verify-standard-library-dependencies.yml
@@ -0,0 +1,81 @@
+name: Verify Standard Library Dependencies
+
+# Run this workflow every time the "supported_codeql_configs.json" file or a "qlpack.yml" file is changed
+on:
+ merge_group:
+ types: [checks_requested]
+ pull_request:
+ branches:
+ - main
+ - "rc/**"
+ - next
+ paths:
+ - "supported_codeql_configs.json"
+ - "**/qlpack.yml"
+ workflow_dispatch:
+
+jobs:
+ prepare-matrix:
+ name: Prepare CodeQL configuration matrix
+ runs-on: ubuntu-22.04
+ outputs:
+ matrix: ${{ steps.export-matrix.outputs.matrix }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Export unit test matrix
+ id: export-matrix
+ run: |
+ echo "::set-output name=matrix::$(
+ jq --compact-output \
+ '.supported_environment | map([.+{os: "ubuntu-20.04-xl", codeql_standard_library_ident : .codeql_standard_library | sub("\/"; "_")}]) | flatten | {include: .}' \
+ supported_codeql_configs.json
+ )"
+
+ verify-dependencies:
+ name: Verify dependencies
+ needs: prepare-matrix
+
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix: ${{fromJSON(needs.prepare-matrix.outputs.matrix)}}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Python 3
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.9"
+
+ - name: Cache CodeQL
+ id: cache-codeql
+ uses: actions/cache@v4
+ with:
+ # A list of files, directories, and wildcard patterns to cache and restore
+ path: ${{github.workspace}}/codeql_home
+ # An explicit key for restoring and saving the cache
+ key: codeql-home-${{matrix.os}}-${{matrix.codeql_cli}}-${{matrix.codeql_standard_library}}
+
+ - name: Install CodeQL
+ if: steps.cache-codeql.outputs.cache-hit != 'true'
+ uses: ./.github/actions/install-codeql
+ with:
+ codeql-cli-version: ${{matrix.codeql_cli}}
+ codeql-stdlib-version: ${{matrix.codeql_standard_library}}
+ codeql-home: ${{ github.workspace }}/codeql_home
+
+ - name: Verify dependencies
+ shell: bash
+ env:
+ CLI_PATH: ${{ github.workspace }}/codeql_home/codeql
+ STDLIB_PATH: ${{ github.workspace }}/codeql_home/codeql-stdlib
+ run: |
+ PATH=$PATH:$CLI_PATH
+ ls $STDLIB_PATH
+ pip install -r scripts/requirements.txt
+ python3 scripts/verify-standard-library-version.py --codeql-repo $STDLIB_PATH --mode verify
+
diff --git a/.gitignore b/.gitignore
index 5466e33c8f..360134b51c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,6 @@
# C/C++ build artifacts
*.o
/databases/
+
+# CodeQL build artifacts
+**/.codeql/**
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index af8560fc44..0000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "codeql"]
- path = codeql_modules/codeql
- url = https://github.com/github/codeql.git
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index d1f141cced..74f065ac3b 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -140,6 +140,28 @@
},
"problemMatcher": []
},
+ {
+ "label": "🧪 Standards Automation: Build Case Test DB from test file",
+ "type": "shell",
+ "windows": {
+ "command": ".${pathSeparator}scripts${pathSeparator}.venv${pathSeparator}Scripts${pathSeparator}python.exe scripts${pathSeparator}build_test_database.py ${file}"
+ },
+ "linux": {
+ "command": ".${pathSeparator}scripts${pathSeparator}.venv${pathSeparator}bin${pathSeparator}python3 scripts${pathSeparator}build_test_database.py ${file}"
+ },
+ "osx": {
+ "command": ".${pathSeparator}scripts${pathSeparator}.venv${pathSeparator}bin${pathSeparator}python3 scripts${pathSeparator}build_test_database.py ${file}"
+ },
+ "presentation": {
+ "reveal": "always",
+ "panel": "new",
+ "focus": true
+ },
+ "runOptions": {
+ "reevaluateOnRerun": false
+ },
+ "problemMatcher": []
+ },
{
"label": "📝 Standards Automation: Format CodeQL",
"type": "shell",
@@ -185,6 +207,7 @@
"type": "pickString",
"options": [
"Allocations",
+ "Banned",
"BannedFunctions",
"BannedLibraries",
"BannedSyntax",
@@ -192,19 +215,30 @@
"Classes",
"Comments",
"Contracts1",
+ "Contracts2",
+ "Contracts3",
+ "Contracts4",
+ "Contracts5",
+ "Contracts6",
"Concurrency",
"Concurrency",
"Concurrency1",
"Concurrency2",
"Concurrency3",
- "Concurrency4",
- "Concurrency5",
+ "Concurrency4",
+ "Concurrency5",
"Conditionals",
"Const",
"DeadCode",
"Declarations",
"Declarations1",
"Declarations2",
+ "Declarations3",
+ "Declarations4",
+ "Declarations5",
+ "Declarations6",
+ "Declarations7",
+ "Declarations8",
"Exceptions1",
"Exceptions2",
"Expressions",
@@ -217,25 +251,37 @@
"Includes",
"Initialization",
"IntegerConversion",
+ "InvalidMemory1",
+ "InvalidMemory2",
"Invariants",
"Iterators",
"Lambdas",
"Language1",
+ "Language2",
+ "Language3",
"Literals",
"Loops",
"Macros",
+ "Memory1",
+ "Memory2",
+ "Memory3",
"Misc",
"MoveForward",
"Naming",
"Null",
"OperatorInvariants",
"Operators",
+ "OrderOfEvaluation",
+ "OutOfBounds",
"Pointers",
"Pointers1",
"Pointers2",
+ "Pointers3",
+ "Representation",
"Scope",
"SideEffects1",
"SideEffects2",
+ "SideEffects3",
"SmartPointers1",
"SmartPointers2",
"Strings",
@@ -252,6 +298,7 @@
"Preprocessor3",
"Preprocessor4",
"Preprocessor5",
+ "Preprocessor6",
"IntegerConversion",
"Expressions",
"DeadCode",
diff --git a/README.md b/README.md
index 06668c6676..02c226f84a 100644
--- a/README.md
+++ b/README.md
@@ -6,17 +6,24 @@ This repository contains CodeQL queries and libraries which support various Codi
_Carnegie Mellon and CERT are registered trademarks of Carnegie Mellon University._
-This repository contains CodeQL queries and libraries which support various Coding Standards for the [C++14](https://www.iso.org/standard/64029.html) programming language.
+This repository contains CodeQL queries and libraries which support various Coding Standards for the [C++14](https://www.iso.org/standard/64029.html), [C99](https://www.iso.org/standard/29237.html) and [C11](https://www.iso.org/standard/57853.html) programming languages.
The following coding standards are supported:
-- [AUTOSAR - Guidelines for the use of C++14 language in critical and safety-related systems Release 20-11](https://www.autosar.org/fileadmin/user_upload/standards/adaptive/20-11/AUTOSAR_RS_CPP14Guidelines.pdf)
-- [MISRA C++:2008](https://www.misra.org.uk) (support limited to the rules specified in AUTOSAR 20-11).
+- [AUTOSAR - Guidelines for the use of C++14 language in critical and safety-related systems (Releases R22-11, R20-11, R19-11 and R19-03)](https://www.autosar.org/fileadmin/standards/R22-11/AP/AUTOSAR_RS_CPP14Guidelines.pdf).
- [SEI CERT C++ Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition)](https://resources.sei.cmu.edu/library/asset-view.cfm?assetID=494932)
+- [SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition)](https://resources.sei.cmu.edu/downloads/secure-coding/assets/sei-cert-c-coding-standard-2016-v01.pdf)
+- [MISRA C 2012, 3rd Edition, 1st revision](https://www.misra.org.uk/product/misra-c2012-third-edition-first-revision/) (incoporating Amendment 1 & Technical Corrigendum 1). In addition, we support the following additional amendments and technical corrigendums:
+ - [MISRA C 2012 Amendment 2](https://misra.org.uk/app/uploads/2021/06/MISRA-C-2012-AMD2.pdf)
+ - [MISRA C 2012 Technical Corrigendum 2](https://misra.org.uk/app/uploads/2022/04/MISRA-C-2012-TC2.pdf)
+ - [MISRA C 2012 Amendment 3](https://misra.org.uk/app/uploads/2021/06/MISRA-C-2012-AMD3.pdf)
+ - [MISRA C 2012 Amendment 4](https://misra.org.uk/app/uploads/2021/06/MISRA-C-2012-AMD4.pdf)
+- [MISRA C 2023](https://misra.org.uk/product/misra-c2023/)
-In addition, the following Coding Standards for the C programming language are under development:
+## :construction: Standards under development :construction:
-- [SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition)](https://resources.sei.cmu.edu/downloads/secure-coding/assets/sei-cert-c-coding-standard-2016-v01.pdf)
-- [MISRA C 2012](https://www.misra.org.uk/product/misra-c2012-third-edition-first-revision/).
+The following standards are under active development for [C++17](https://www.iso.org/standard/68564.html):
+
+- [MISRA C++ 2023](https://misra.org.uk/product/misra-cpp2023/) - under development - _scheduled for release 2025 Q2/Q3_
## How do I use the CodeQL Coding Standards Queries?
@@ -50,3 +57,5 @@ All header files in [c/common/test/includes/standard-library](./c/common/test/in
---
1This repository incorporates portions of the SEI CERT® Coding Standards available at https://wiki.sei.cmu.edu/confluence/display/seccode/SEI+CERT+Coding+Standards; however, such use does not necessarily constitute or imply an endorsement, recommendation, or favoring by Carnegie Mellon University or its Software Engineering Institute.
+
+
diff --git a/amendments.csv b/amendments.csv
new file mode 100644
index 0000000000..7bcc65327c
--- /dev/null
+++ b/amendments.csv
@@ -0,0 +1,49 @@
+language,standard,amendment,rule_id,supportable,implementation_category,implemented,difficulty
+c,MISRA-C-2012,Amendment3,DIR-4-6,Yes,Expand,Yes,Easy
+c,MISRA-C-2012,Amendment3,DIR-4-9,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Amendment3,DIR-4-11,Yes,Refine,Yes,Import
+c,MISRA-C-2012,Amendment3,RULE-1-4,Yes,Replace,Yes,Easy
+c,MISRA-C-2012,Amendment3,RULE-10-1,Yes,Replace,Yes,Easy
+c,MISRA-C-2012,Amendment3,RULE-10-3,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Amendment3,RULE-10-4,Yes,Refine,Yes,Import
+c,MISRA-C-2012,Amendment3,RULE-10-5,Yes,Expand,Yes,Easy
+c,MISRA-C-2012,Amendment3,RULE-10-7,Yes,Refine,Yes,Import
+c,MISRA-C-2012,Amendment3,RULE-10-8,Yes,Refine,Yes,Import
+c,MISRA-C-2012,Amendment3,RULE-21-11,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Amendment3,RULE-21-12,Yes,Replace,Yes,Easy
+c,MISRA-C-2012,Amendment4,RULE-11-3,Yes,Expand,Yes,Easy
+c,MISRA-C-2012,Amendment4,RULE-11-8,Yes,Expand,Yes,Easy
+c,MISRA-C-2012,Amendment4,RULE-13-2,Yes,Expand,Yes,Very Hard
+c,MISRA-C-2012,Amendment4,RULE-18-6,Yes,Expand,Yes,Medium
+c,MISRA-C-2012,Amendment4,RULE-18-8,Yes,Split,Yes,Easy
+c,MISRA-C-2012,Amendment4,RULE-2-2,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Amendment4,RULE-2-7,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Amendment4,RULE-3-1,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Amendment4,RULE-8-6,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Amendment4,RULE-8-9,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Amendment4,RULE-9-4,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Amendment4,RULE-10-1,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Amendment4,RULE-18-3,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Amendment4,RULE-1-4,Yes,Replace,Yes,Easy
+c,MISRA-C-2012,Amendment4,RULE-9-1,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Corrigendum2,DIR-4-10,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-7-4,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Corrigendum2,RULE-8-2,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-8-3,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Corrigendum2,RULE-8-7,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-10-1,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-10-2,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Corrigendum2,RULE-10-3,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-11-3,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-11-6,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-13-2,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-13-6,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-14-3,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Corrigendum2,RULE-15-7,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-17-4,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-17-5,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-18-1,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Corrigendum2,RULE-20-14,No,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-21-19,Yes,Clarification,Yes,Import
+c,MISRA-C-2012,Corrigendum2,RULE-21-20,Yes,Refine,Yes,Easy
+c,MISRA-C-2012,Corrigendum2,RULE-22-9,Yes,Clarification,Yes,Import
diff --git a/apply-configuration/action.yml b/apply-configuration/action.yml
new file mode 100644
index 0000000000..89a702b72a
--- /dev/null
+++ b/apply-configuration/action.yml
@@ -0,0 +1,24 @@
+name: Applies Coding Standard configuration files in the repository
+description: |
+ Installs Python and indexes the CodeQL Coding Standard configuration files in the repository
+
+runs:
+ using: composite
+ steps:
+ - name: Install Python
+ id: cs-install-python
+ uses: actions/setup-python@v5
+ with:
+ python-version: 3.9
+ update-environment: false
+ - name: Install dependencies and process files
+ shell: bash
+ run: |
+ install_dir=$(dirname $(dirname "${{ steps.cs-install-python.outputs.python-path }}"))
+ if [[ -z "$LD_LIBRARY_PATH" ]]; then
+ export LD_LIBRARY_PATH="$install_dir/lib"
+ else
+ export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$install_dir/lib"
+ fi
+ ${{ steps.cs-install-python.outputs.python-path }} -m pip install -r ${GITHUB_ACTION_PATH}/../scripts/configuration/requirements.txt
+ ${{ steps.cs-install-python.outputs.python-path }} ${GITHUB_ACTION_PATH}/../scripts/configuration/process_coding_standards_config.py
\ No newline at end of file
diff --git a/c/.codeqlmanifest.json b/c/.codeqlmanifest.json
deleted file mode 100644
index 384848fdd1..0000000000
--- a/c/.codeqlmanifest.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{ "provide": [
- "*/src/qlpack.yml",
- "*/test/qlpack.yml" ] }
diff --git a/c/cert/src/codeql-pack.lock.yml b/c/cert/src/codeql-pack.lock.yml
new file mode 100644
index 0000000000..a45ea8f438
--- /dev/null
+++ b/c/cert/src/codeql-pack.lock.yml
@@ -0,0 +1,24 @@
+---
+lockVersion: 1.0.0
+dependencies:
+ codeql/cpp-all:
+ version: 4.0.3
+ codeql/dataflow:
+ version: 2.0.3
+ codeql/mad:
+ version: 1.0.19
+ codeql/rangeanalysis:
+ version: 1.0.19
+ codeql/ssa:
+ version: 1.0.19
+ codeql/tutorial:
+ version: 1.0.19
+ codeql/typeflow:
+ version: 1.0.19
+ codeql/typetracking:
+ version: 2.0.3
+ codeql/util:
+ version: 2.0.6
+ codeql/xml:
+ version: 1.0.19
+compiled: false
diff --git a/c/cert/src/codeql-suites/cert-c-default.qls b/c/cert/src/codeql-suites/cert-c-default.qls
new file mode 100644
index 0000000000..348d2f37ae
--- /dev/null
+++ b/c/cert/src/codeql-suites/cert-c-default.qls
@@ -0,0 +1,10 @@
+- description: CERT C 2016 (Default)
+- qlpack: codeql/cert-c-coding-standards
+- include:
+ kind:
+ - problem
+ - path-problem
+ - external/cert/obligation/rule
+- exclude:
+ tags contain:
+ - external/cert/default-disabled
diff --git a/c/cert/src/codeql-suites/cert-c-l1.qls b/c/cert/src/codeql-suites/cert-c-l1.qls
new file mode 100644
index 0000000000..b2056fbec8
--- /dev/null
+++ b/c/cert/src/codeql-suites/cert-c-l1.qls
@@ -0,0 +1,12 @@
+- description: CERT C 2016 Level 1 Rules (Priority 12 - Priority 27)
+- qlpack: codeql/cert-c-coding-standards
+- include:
+ kind:
+ - problem
+ - path-problem
+ - external/cert/obligation/rule
+ tags contain:
+ - external/cert/level/l1
+- exclude:
+ tags contain:
+ - external/cert/default-disabled
\ No newline at end of file
diff --git a/c/cert/src/codeql-suites/cert-c-l2.qls b/c/cert/src/codeql-suites/cert-c-l2.qls
new file mode 100644
index 0000000000..9c0a4b1ef9
--- /dev/null
+++ b/c/cert/src/codeql-suites/cert-c-l2.qls
@@ -0,0 +1,12 @@
+- description: CERT C 2016 Level 2 Rules (Priority 6 - Priority 9)
+- qlpack: codeql/cert-c-coding-standards
+- include:
+ kind:
+ - problem
+ - path-problem
+ - external/cert/obligation/rule
+ tags contain:
+ - external/cert/level/l2
+- exclude:
+ tags contain:
+ - external/cert/default-disabled
\ No newline at end of file
diff --git a/c/cert/src/codeql-suites/cert-c-l3.qls b/c/cert/src/codeql-suites/cert-c-l3.qls
new file mode 100644
index 0000000000..462a6d816f
--- /dev/null
+++ b/c/cert/src/codeql-suites/cert-c-l3.qls
@@ -0,0 +1,12 @@
+- description: CERT C 2016 Level 3 Rules (Priority 1 - Priority 4)
+- qlpack: codeql/cert-c-coding-standards
+- include:
+ kind:
+ - problem
+ - path-problem
+ - external/cert/obligation/rule
+ tags contain:
+ - external/cert/level/l3
+- exclude:
+ tags contain:
+ - external/cert/default-disabled
\ No newline at end of file
diff --git a/c/cert/src/codeql-suites/cert-c-recommendation.qls b/c/cert/src/codeql-suites/cert-c-recommendation.qls
new file mode 100644
index 0000000000..59ac5e9c2d
--- /dev/null
+++ b/c/cert/src/codeql-suites/cert-c-recommendation.qls
@@ -0,0 +1,10 @@
+- description: CERT C 2016 (Recommendations)
+- qlpack: codeql/cert-c-coding-standards
+- include:
+ kind:
+ - problem
+ - path-problem
+ - external/cert/obligation/recommendation
+- exclude:
+ tags contain:
+ - external/cert/default-disabled
diff --git a/c/cert/src/codeql-suites/cert-default.qls b/c/cert/src/codeql-suites/cert-default.qls
index c8652930e9..c093b31fa7 100644
--- a/c/cert/src/codeql-suites/cert-default.qls
+++ b/c/cert/src/codeql-suites/cert-default.qls
@@ -1,9 +1,2 @@
-- description: CERT C 2016 (Default)
-- qlpack: cert-c-coding-standards
-- include:
- kind:
- - problem
- - path-problem
-- exclude:
- tags contain:
- - external/cert/default-disabled
\ No newline at end of file
+- description: "DEPRECATED - CERT C 2016 - use cert-c-default.qls instead"
+- import: codeql-suites/cert-c-default.qls
diff --git a/c/cert/src/qlpack.yml b/c/cert/src/qlpack.yml
index 9b8b4c6d56..f79744a4fc 100644
--- a/c/cert/src/qlpack.yml
+++ b/c/cert/src/qlpack.yml
@@ -1,4 +1,9 @@
-name: cert-c-coding-standards
-version: 2.9.0
+name: codeql/cert-c-coding-standards
+version: 2.49.0-dev
+description: CERT C 2016
suites: codeql-suites
-libraryPathDependencies: common-c-coding-standards
\ No newline at end of file
+license: MIT
+default-suite-file: codeql-suites/cert-c-default.qls
+dependencies:
+ codeql/common-c-coding-standards: '*'
+ codeql/cpp-all: 4.0.3
diff --git a/c/cert/src/rules/ARR30-C/DoNotFormOutOfBoundsPointersOrArraySubscripts.md b/c/cert/src/rules/ARR30-C/DoNotFormOutOfBoundsPointersOrArraySubscripts.md
new file mode 100644
index 0000000000..221b008786
--- /dev/null
+++ b/c/cert/src/rules/ARR30-C/DoNotFormOutOfBoundsPointersOrArraySubscripts.md
@@ -0,0 +1,485 @@
+# ARR30-C: Do not form or use out-of-bounds pointers or array subscripts
+
+This query implements the CERT-C rule ARR30-C:
+
+> Do not form or use out-of-bounds pointers or array subscripts
+
+
+## Description
+
+The C Standard identifies the following distinct situations in which undefined behavior (UB) can arise as a result of invalid pointer operations:
+
+
UB | Description | Example Code |
46 | Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that does not point into, or just beyond, the same array object. | Forming Out-of-Bounds Pointer , Null Pointer Arithmetic |
47 | Addition or subtraction of a pointer into, or just beyond, an array object and an integer type produces a result that points just beyond the array object and is used as the operand of a unary \* operator that is evaluated. | Dereferencing Past the End Pointer , Using Past the End Index |
49 | An array subscript is out of range, even if an object is apparently accessible with the given subscript, for example, in the lvalue expression a\[1\]\[7\] given the declaration int a\[4\]\[5\] ). | Apparently Accessible Out-of-Range Index |
62 | An attempt is made to access, or generate a pointer to just past, a flexible array member of a structure when the referenced object provides no elements for that array. | Pointer Past Flexible Array Member |
+
+
+## Noncompliant Code Example (Forming Out-of-Bounds Pointer)
+
+In this noncompliant code example, the function `f()` attempts to validate the `index` before using it as an offset to the statically allocated `table` of integers. However, the function fails to reject negative `index` values. When `index` is less than zero, the behavior of the addition expression in the return statement of the function is [undefined behavior 46](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_46). On some implementations, the addition alone can trigger a hardware trap. On other implementations, the addition may produce a result that when dereferenced triggers a hardware trap. Other implementations still may produce a dereferenceable pointer that points to an object distinct from `table`. Using such a pointer to access the object may lead to information exposure or cause the wrong object to be modified.
+
+```cpp
+enum { TABLESIZE = 100 };
+
+static int table[TABLESIZE];
+
+int *f(int index) {
+ if (index < TABLESIZE) {
+ return table + index;
+ }
+ return NULL;
+}
+
+```
+
+## Compliant Solution
+
+One compliant solution is to detect and reject invalid values of `index` if using them in pointer arithmetic would result in an invalid pointer:
+
+```cpp
+enum { TABLESIZE = 100 };
+
+static int table[TABLESIZE];
+
+int *f(int index) {
+ if (index >= 0 && index < TABLESIZE) {
+ return table + index;
+ }
+ return NULL;
+}
+
+```
+
+## Compliant Solution
+
+Another slightly simpler and potentially more efficient compliant solution is to use an unsigned type to avoid having to check for negative values while still rejecting out-of-bounds positive values of `index`:
+
+```cpp
+#include
+
+enum { TABLESIZE = 100 };
+
+static int table[TABLESIZE];
+
+int *f(size_t index) {
+ if (index < TABLESIZE) {
+ return table + index;
+ }
+ return NULL;
+}
+
+```
+
+## Noncompliant Code Example (Dereferencing Past-the-End Pointer)
+
+This noncompliant code example shows the flawed logic in the Windows Distributed Component Object Model (DCOM) Remote Procedure Call (RPC) interface that was exploited by the W32.Blaster.Worm. The error is that the `while` loop in the `GetMachineName()` function (used to extract the host name from a longer string) is not sufficiently bounded. When the character array pointed to by `pwszTemp` does not contain the backslash character among the first `MAX_COMPUTERNAME_LENGTH_FQDN + 1` elements, the final valid iteration of the loop will dereference past the end pointer, resulting in exploitable [undefined behavior 47](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_47). In this case, the actual exploit allowed the attacker to inject executable code into a running program. Economic damage from the Blaster worm has been estimated to be at least $525 million \[[Pethia 2003](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Pethia03)\].
+
+For a discussion of this programming error in the Common Weakness Enumeration database, see [CWE-119](http://cwe.mitre.org/data/definitions/119.html), "Improper Restriction of Operations within the Bounds of a Memory Buffer," and [CWE-121](http://cwe.mitre.org/data/definitions/121.html), "Stack-based Buffer Overflow" \[[MITRE 2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-MITRE)\].
+
+```cpp
+error_status_t _RemoteActivation(
+ /* ... */, WCHAR *pwszObjectName, ... ) {
+ *phr = GetServerPath(
+ pwszObjectName, &pwszObjectName);
+ /* ... */
+}
+
+HRESULT GetServerPath(
+ WCHAR *pwszPath, WCHAR **pwszServerPath ){
+ WCHAR *pwszFinalPath = pwszPath;
+ WCHAR wszMachineName[MAX_COMPUTERNAME_LENGTH_FQDN+1];
+ hr = GetMachineName(pwszPath, wszMachineName);
+ *pwszServerPath = pwszFinalPath;
+}
+
+HRESULT GetMachineName(
+ WCHAR *pwszPath,
+ WCHAR wszMachineName[MAX_COMPUTERNAME_LENGTH_FQDN+1])
+{
+ pwszServerName = wszMachineName;
+ LPWSTR pwszTemp = pwszPath + 2;
+ while (*pwszTemp != L'\\')
+ *pwszServerName++ = *pwszTemp++;
+ /* ... */
+}
+
+```
+
+## Compliant Solution
+
+In this compliant solution, the `while` loop in the `GetMachineName()` function is bounded so that the loop terminates when a backslash character is found, the null-termination character (`L'\0'`) is discovered, or the end of the buffer is reached. Or, as coded, the while loop continues as long as each character is neither a backslash nor a null character and is not at the end of the buffer. This code does not result in a buffer overflow even if no backslash character is found in `wszMachineName`.
+
+```cpp
+HRESULT GetMachineName(
+ wchar_t *pwszPath,
+ wchar_t wszMachineName[MAX_COMPUTERNAME_LENGTH_FQDN+1])
+{
+ wchar_t *pwszServerName = wszMachineName;
+ wchar_t *pwszTemp = pwszPath + 2;
+ wchar_t *end_addr
+ = pwszServerName + MAX_COMPUTERNAME_LENGTH_FQDN;
+ while ((*pwszTemp != L'\\') &&
+ (*pwszTemp != L'\0') &&
+ (pwszServerName < end_addr))
+ {
+ *pwszServerName++ = *pwszTemp++;
+ }
+
+ /* ... */
+}
+
+```
+This compliant solution is for illustrative purposes and is not necessarily the solution implemented by Microsoft. This particular solution may not be correct because there is no guarantee that a backslash is found.
+
+## Noncompliant Code Example (Using Past-the-End Index)
+
+Similar to the [dereferencing-past-the-end-pointer](https://wiki.sei.cmu.edu/confluence/display/c/ARR30-C.+Do+not+form+or+use+out-of-bounds+pointers+or+array+subscripts#ARR30C.Donotformoruseoutofboundspointersorarraysubscripts-DereferencingPasttheEndPointer) error, the function `insert_in_table()` in this noncompliant code example uses an otherwise valid index to attempt to store a value in an element just past the end of an array.
+
+First, the function incorrectly validates the index `pos` against the size of the buffer. When `pos` is initially equal to `size`, the function attempts to store `value` in a memory location just past the end of the buffer.
+
+Second, when the index is greater than `size`, the function modifies `size` before growing the size of the buffer. If the call to `realloc()` fails to increase the size of the buffer, the next call to the function with a value of `pos` equal to or greater than the original value of `size` will again attempt to store `value` in a memory location just past the end of the buffer or beyond.
+
+Third, the function violates [INT30-C. Ensure that unsigned integer operations do not wrap](https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap), which could lead to wrapping when 1 is added to `pos` or when `size` is multiplied by the size of `int`.
+
+For a discussion of this programming error in the Common Weakness Enumeration database, see [CWE-122](http://cwe.mitre.org/data/definitions/122.html), "Heap-based Buffer Overflow," and [CWE-129](http://cwe.mitre.org/data/definitions/129.html), "Improper Validation of Array Index" \[[MITRE 2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-MITRE)\].
+
+```cpp
+#include
+
+static int *table = NULL;
+static size_t size = 0;
+
+int insert_in_table(size_t pos, int value) {
+ if (size < pos) {
+ int *tmp;
+ size = pos + 1;
+ tmp = (int *)realloc(table, sizeof(*table) * size);
+ if (tmp == NULL) {
+ return -1; /* Failure */
+ }
+ table = tmp;
+ }
+
+ table[pos] = value;
+ return 0;
+}
+
+```
+
+## Compliant Solution
+
+This compliant solution correctly validates the index `pos` by using the `<=` relational operator, ensures the multiplication will not overflow, and avoids modifying `size` until it has verified that the call to `realloc()` was successful:
+
+```cpp
+#include
+#include
+
+static int *table = NULL;
+static size_t size = 0;
+
+int insert_in_table(size_t pos, int value) {
+ if (size <= pos) {
+ if ((SIZE_MAX - 1 < pos) ||
+ ((pos + 1) > SIZE_MAX / sizeof(*table))) {
+ return -1;
+ }
+
+ int *tmp = (int *)realloc(table, sizeof(*table) * (pos + 1));
+ if (tmp == NULL) {
+ return -1;
+ }
+ /* Modify size only after realloc() succeeds */
+ size = pos + 1;
+ table = tmp;
+ }
+
+ table[pos] = value;
+ return 0;
+}
+
+```
+
+## Noncompliant Code Example (Apparently Accessible Out-of-Range Index)
+
+This noncompliant code example declares `matrix` to consist of 7 rows and 5 columns in row-major order. The function `init_matrix` iterates over all 35 elements in an attempt to initialize each to the value given by the function argument `x`. However, because multidimensional arrays are declared in C in row-major order, the function iterates over the elements in column-major order, and when the value of `j` reaches the value `COLS` during the first iteration of the outer loop, the function attempts to access element `matrix[0][5]`. Because the type of `matrix` is `int[7][5]`, the `j` subscript is out of range, and the access has [undefined behavior 49](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_49).
+
+```cpp
+#include
+#define COLS 5
+#define ROWS 7
+static int matrix[ROWS][COLS];
+
+void init_matrix(int x) {
+ for (size_t i = 0; i < COLS; i++) {
+ for (size_t j = 0; j < ROWS; j++) {
+ matrix[i][j] = x;
+ }
+ }
+}
+
+```
+
+## Compliant Solution
+
+This compliant solution avoids using out-of-range indices by initializing `matrix` elements in the same row-major order as multidimensional objects are declared in C:
+
+```cpp
+#include
+#define COLS 5
+#define ROWS 7
+static int matrix[ROWS][COLS];
+
+void init_matrix(int x) {
+ for (size_t i = 0; i < ROWS; i++) {
+ for (size_t j = 0; j < COLS; j++) {
+ matrix[i][j] = x;
+ }
+ }
+}
+
+```
+
+## Noncompliant Code Example (Pointer Past Flexible Array Member)
+
+In this noncompliant code example, the function `find()` attempts to iterate over the elements of the flexible array member `buf`, starting with the second element. However, because function `g()` does not allocate any storage for the member, the expression `first++` in `find()` attempts to form a pointer just past the end of `buf` when there are no elements. This attempt is [undefined behavior 62](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_62). (See [MSC21-C. Use robust loop termination conditions](https://wiki.sei.cmu.edu/confluence/display/c/MSC21-C.+Use+robust+loop+termination+conditions) for more information.)
+
+```cpp
+#include
+
+struct S {
+ size_t len;
+ char buf[]; /* Flexible array member */
+};
+
+const char *find(const struct S *s, int c) {
+ const char *first = s->buf;
+ const char *last = s->buf + s->len;
+
+ while (first++ != last) { /* Undefined behavior */
+ if (*first == (unsigned char)c) {
+ return first;
+ }
+ }
+ return NULL;
+}
+
+void g(void) {
+ struct S *s = (struct S *)malloc(sizeof(struct S));
+ if (s == NULL) {
+ /* Handle error */
+ }
+ s->len = 0;
+ find(s, 'a');
+}
+```
+
+## Compliant Solution
+
+This compliant solution avoids incrementing the pointer unless a value past the pointer's current value is known to exist:
+
+```cpp
+#include
+
+struct S {
+ size_t len;
+ char buf[]; /* Flexible array member */
+};
+
+const char *find(const struct S *s, int c) {
+ const char *first = s->buf;
+ const char *last = s->buf + s->len;
+
+ while (first != last) { /* Avoid incrementing here */
+ if (*++first == (unsigned char)c) {
+ return first;
+ }
+ }
+ return NULL;
+}
+
+void g(void) {
+ struct S *s = (struct S *)malloc(sizeof(struct S));
+ if (s == NULL) {
+ /* Handle error */
+ }
+ s->len = 0;
+ find(s, 'a');
+}
+```
+
+## Noncompliant Code Example (Null Pointer Arithmetic)
+
+This noncompliant code example is similar to an [Adobe Flash Player vulnerability](http://www.iss.net/threats/289.html) that was first exploited in 2008. This code allocates a block of memory and initializes it with some data. The data does not belong at the beginning of the block, which is left uninitialized. Instead, it is placed `offset` bytes within the block. The function ensures that the data fits within the allocated block.
+
+```cpp
+#include
+#include
+
+char *init_block(size_t block_size, size_t offset,
+ char *data, size_t data_size) {
+ char *buffer = malloc(block_size);
+ if (data_size > block_size || block_size - data_size < offset) {
+ /* Data won't fit in buffer, handle error */
+ }
+ memcpy(buffer + offset, data, data_size);
+ return buffer;
+}
+```
+This function fails to check if the allocation succeeds, which is a violation of [ERR33-C. Detect and handle standard library errors](https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors). If the allocation fails, then `malloc()` returns a null pointer. The null pointer is added to `offset` and passed as the destination argument to `memcpy()`. Because a null pointer does not point to a valid object, the result of the pointer arithmetic is [undefined behavior 46](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_46).
+
+An attacker who can supply the arguments to this function can exploit it to execute arbitrary code. This can be accomplished by providing an overly large value for `block_size`, which causes `malloc()` to fail and return a null pointer. The `offset` argument will then serve as the destination address to the call to `memcpy()`. The attacker can specify the `data` and `data_size` arguments to provide the address and length of the address, respectively, that the attacker wishes to write into the memory referenced by `offset`. The overall result is that the call to `memcpy()` can be exploited by an attacker to overwrite an arbitrary memory location with an attacker-supplied address, typically resulting in arbitrary code execution.
+
+## Compliant Solution (Null Pointer Arithmetic)
+
+This compliant solution ensures that the call to `malloc()` succeeds:
+
+```cpp
+#include
+#include
+
+char *init_block(size_t block_size, size_t offset,
+ char *data, size_t data_size) {
+ char *buffer = malloc(block_size);
+ if (NULL == buffer) {
+ /* Handle error */
+ }
+ if (data_size > block_size || block_size - data_size < offset) {
+ /* Data won't fit in buffer, handle error */
+ }
+ memcpy(buffer + offset, data, data_size);
+ return buffer;
+}
+
+```
+
+## Risk Assessment
+
+Writing to out-of-range pointers or array subscripts can result in a buffer overflow and the execution of arbitrary code with the permissions of the vulnerable process. Reading from out-of-range pointers or array subscripts can result in unintended information disclosure.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ARR30-C | High | Likely | High | P9 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | array-index-rangearray-index-range-constantnull-dereferencingpointered-deallocation return-reference-local | Partially checked Can detect all accesses to invalid pointers as well as array index out-of-bounds accesses and prove their absence. This rule is only partially checked as invalid but unused pointers may not be reported. |
Axivion Bauhaus Suite | 7.2.0 | CertC-ARR30 | Can detect out-of-bound access to array / buffer |
CodeSonar | 7.3p0 | LANG.MEM.BO LANG.MEM.BU LANG.MEM.TBA LANG.MEM.TO LANG.MEM.TULANG.STRUCT.PARITH LANG.STRUCT.PBB LANG.STRUCT.PPE BADFUNC.BO.\* | Buffer overrun Buffer underrun Tainted buffer access Type overrun Type underrun Pointer Arithmetic Pointer before beginning of object Pointer past end of object A collection of warning classes that report uses of library functions prone to internal buffer overflows. |
Compass/ROSE | | | Could be configured to catch violations of this rule. The way to catch the noncompliant code example is to first hunt for example code that follows this pattern: for (LPWSTR pwszTemp = pwszPath + 2; \*pwszTemp != L'\\\\'; \*pwszTemp++;) In particular, the iteration variable is a pointer, it gets incremented, and the loop condition does not set an upper bound on the pointer. Once this case is handled, ROSE can handle cases like the real noncompliant code example, which is effectively the same semantics, just different syntax |
Coverity | 2017.07 | OVERRUN NEGATIVE_RETURNS ARRAY_VS_SINGLETON BUFFER_SIZE | Can detect the access of memory past the end of a memory buffer/array Can detect when the loop bound may become negative Can detect the out-of-bound read/write to array allocated statically or dynamically Can detect buffer overflows |
Cppcheck | 1.66 | arrayIndexOutOfBounds, outOfBounds, negativeIndex, arrayIndexThenCheck, arrayIndexOutOfBoundsCond, possibleBufferAccessOutOfBounds | Context sensitive analysis of array index, pointers, etc. Array index out of bounds Buffer overflow when calling various functions memset,strcpy,.. Warns about condition (a\[i\] == 0 && i < unknown_value) and recommends that (i < unknown_value && a\[i\] == 0) is used instead Detects unsafe code when array is accessed before/after it is tested if the array index is out of bounds |
Helix QAC | 2023.1 | C2840 DF2820, DF2821, DF2822, DF2823, DF2840, DF2841, DF2842, DF2843, DF2930, DF2931, DF2932, DF2933, DF2935, DF2936, DF2937, DF2938, DF2950, DF2951, DF2952, DF2953 | |
Klocwork | 2023.1 | ABV.GENERAL ABV.GENERAL.MULTIDIMENSION NPD.FUNC.CALL.MIGHT ABV.ANY_SIZE_ARRAY ABV.STACK ABV.TAINTED ABV.UNICODE.BOUND_MAP ABV.UNICODE.FAILED_MAP ABV.UNICODE.NNTS_MAP ABV.UNICODE.SELF_MAP ABV.UNKNOWN_SIZE NNTS.MIGHT NNTS.MUST NNTS.TAINTED SV.TAINTED.INDEX_ACCESS SV.TAINTED.LOOP_BOUND | |
LDRA tool suite | 9.7.1 | 45 D, 47 S, 476 S, 489 S, 64 X, 66 X, 68 X, 69 X, 70 X, 71 X , 79 X | Partially implemented |
Parasoft C/C++test | 2022.2 | CERT_C-ARR30-a | Avoid accessing arrays out of bounds |
Parasoft Insure++ | | | Runtime analysis |
PC-lint Plus | 1.4 | 413, 415, 416, 613, 661, 662, 676 | Fully supported |
Polyspace Bug Finder | R2023a | CERT C: Rule ARR30-C | Checks for: Array access out of boundsrray access out of bounds, pointer access out of boundsointer access out of bounds, array access with tainted indexrray access with tainted index, use of tainted pointerse of tainted pointer, pointer dereference with tainted offsetointer dereference with tainted offset. Rule partially covered. |
PRQA QA-C | 9.7 | 2820, 2821, 2822, 2823, 2840, 2841, 2842, 2843, 2930, 2931, 2932, 2933, 2935, 2936, 2937, 2938, 2950, 2951, 2952, 2953 | Partially implemented |
PRQA QA-C++ | 4.4 | 2820, 2821, 2822, 2823, 2840, 2841, 2842, 2843, 2930, 2931, 2932, 2933, 2935, 2936, 2937, 2938, 2950, 2951, 2952, 2953 | Partially implemented |
PVS-Studio | 7.24 | V512 , V557 , V582 , V594 , V643 , V645 , V694, V1086 | |
RuleChecker | 22.04 | array-index-range-constantreturn-reference-local | Partially checked |
TrustInSoft Analyzer | 1.38 | index_in_address | Exhaustively verified (see one compliant and one non-compliant example ). |
+
+
+## Related Vulnerabilities
+
+[CVE-2008-1517](http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2008-1517) results from a violation of this rule. Before Mac OSX version 10.5.7, the XNU kernel accessed an array at an unverified user-input index, allowing an attacker to execute arbitrary code by passing an index greater than the length of the array and therefore accessing outside memory \[[xorl 2009](http://xorl.wordpress.com/2009/06/09/cve-2008-1517-apple-mac-os-x-xnu-missing-array-index-validation/)\].
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ARR30-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+ Taxonomy | Taxonomy item | Relationship |
ISO/IEC TR 24772:2013 | Arithmetic Wrap-Around Error \[FIF\] | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TR 24772:2013 | Unchecked Array Indexing \[XYZ\] | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TS 17961 | Forming or using out-of-bounds pointers or array subscripts \[invptr\] | Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2.11 | CWE-119 , Improper Restriction of Operations within the Bounds of a Memory Buffer | 2017-05-18: CERT: Rule subset of CWE |
CWE 2.11 | CWE-123 , Write-what-where Condition | 2017-05-18: CERT: Partial overlap |
CWE 2.11 | CWE-125 , Out-of-bounds Read | 2017-05-18: CERT: Partial overlap |
MISRA C:2012 | Rule 18.1 (required) | Prior to 2018-01-12: CERT: Unspecified Relationship |
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-119 and ARR30-C**
+
+Independent( ARR30-C, ARR38-C, ARR32-C, INT30-C, INT31-C, EXP39-C, EXP33-C, FIO37-C)
+
+STR31-C = Subset( Union( ARR30-C, ARR38-C))
+
+STR32-C = Subset( ARR38-C)
+
+CWE-119 = Union( ARR30-C, ARR38-C)
+
+Intersection( ARR30-C, ARR38-C) = Ø
+
+**CWE-394 and ARR30-C**
+
+Intersection( ARR30-C, CWE-394) = Ø
+
+CWE-394 deals with potentially-invalid function return values. Which may be used as an (invalid) array index, but validating the return value is a separate operation.
+
+**CWE-125 and ARR30-C**
+
+Independent( ARR30-C, ARR38-C, EXP39-C, INT30-C)
+
+STR31-C = Subset( Union( ARR30-C, ARR38-C))
+
+STR32-C = Subset( ARR38-C)
+
+CWE-125 = Subset( CWE-119) = Union( ARR30-C, ARR38-C)
+
+Intersection( ARR30-C, CWE-125) =
+
+* Reading from an out-of-bounds array index, or off the end of an array
+ARR30-C – CWE-125 =
+* Writing to an out-of-bounds array index, or off the end of an array
+CWE-125 – ARR30-C =
+* Reading beyond a non-array buffer
+* Using a library function to achieve an out-of-bounds read.
+**CWE-123 and ARR30-C**
+
+Independent(ARR30-C, ARR38-C)
+
+STR31-C = Subset( Union( ARR30-C, ARR38-C))
+
+STR32-C = Subset( ARR38-C)
+
+Intersection( CWE-123, ARR30-C) =
+
+* Write of arbitrary value to arbitrary (probably invalid) array index
+ARR30-C – CWE-123 =
+* Read of value from arbitrary (probably invalid) array index
+* Construction of invalid index (pointer arithmetic)
+CWE-123 – ARR30-C =
+* Arbitrary writes that do not involve directly constructing an invalid array index
+**CWE-129 and ARR30-C**
+
+Independent( ARR30-C, ARR32-C, INT31-C, INT32-C)
+
+ARR30-C = Union( CWE-129, list), where list =
+
+* Dereferencing an out-of-bounds array index, where index is a trusted value
+* Forming an out-of-bounds array index, without dereferencing it, whether or not index is a trusted value. (This excludes the array’s TOOFAR index, which is one past the final element; this behavior is well-defined in C11.)
+**CWE-120 and ARR30-C**
+
+See CWE-120 and MEM35-C
+
+**CWE-122 and ARR30-C**
+
+Intersection( ARR30-C, CWE-122) = Ø
+
+CWE-122 specifically addresses buffer overflows on the heap operations, which occur in the context of string-copying. ARR30 specifically addresses improper creation or references of array indices. Which might happen as part of a heap buffer overflow, but is on a lower programming level.
+
+**CWE-20 and ARR30-C**
+
+See CWE-20 and ERR34-C
+
+**CWE-687 and ARR30-C**
+
+Intersection( CWE-687, ARR30-C) = Ø
+
+ARR30-C is about invalid array indices which are created through pointer arithmetic, and dereferenced through an operator (\* or \[\]). Neither involve function calls, thus CWE-687 does not apply.
+
+**CWE-786 and ARR30-C**
+
+ARR30-C = Union( CWE-786, list) where list =
+
+* Access of memory location after end of buffer
+* Construction of invalid arry reference (pointer). This does not include an out-of-bounds array index (an integer).
+**CWE-789 and ARR30-C**
+
+Intersection( CWE-789, ARR30-C) = Ø
+
+CWE-789 is about allocating memory, not array subscripting
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ARR30-C: Do not form or use out-of-bounds pointers or array subscripts](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ARR30-C/DoNotFormOutOfBoundsPointersOrArraySubscripts.ql b/c/cert/src/rules/ARR30-C/DoNotFormOutOfBoundsPointersOrArraySubscripts.ql
new file mode 100644
index 0000000000..fed579bf34
--- /dev/null
+++ b/c/cert/src/rules/ARR30-C/DoNotFormOutOfBoundsPointersOrArraySubscripts.ql
@@ -0,0 +1,58 @@
+/**
+ * @id c/cert/do-not-form-out-of-bounds-pointers-or-array-subscripts
+ * @name ARR30-C: Do not form or use out-of-bounds pointers or array subscripts
+ * @description Forming or using an out-of-bounds pointer is undefined behavior and can result in
+ * invalid memory accesses.
+ * @kind problem
+ * @precision medium
+ * @problem.severity error
+ * @tags external/cert/id/arr30-c
+ * correctness
+ * security
+ * external/cert/severity/high
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p9
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.OutOfBounds
+
+from
+ OOB::BufferAccess ba, Expr bufferArg, Expr sizeArg, OOB::PointerToObjectSource bufferSource,
+ string message
+where
+ not isExcluded(ba, OutOfBoundsPackage::doNotFormOutOfBoundsPointersOrArraySubscriptsQuery()) and
+ // exclude loops
+ not exists(Loop loop | loop.getStmt().getChildStmt*() = ba.getEnclosingStmt()) and
+ // exclude size arguments that are of type ssize_t
+ not sizeArg.getAChild*().(VariableAccess).getTarget().getType() instanceof Ssize_t and
+ // exclude size arguments that are assigned the result of a function call e.g. ftell
+ not sizeArg.getAChild*().(VariableAccess).getTarget().getAnAssignedValue() instanceof FunctionCall and
+ // exclude field or array accesses for the size arguments
+ not sizeArg.getAChild*() instanceof FieldAccess and
+ not sizeArg.getAChild*() instanceof ArrayExpr and
+ (
+ exists(int sizeArgValue, int bufferArgSize |
+ OOB::isSizeArgGreaterThanBufferSize(bufferArg, sizeArg, bufferSource, bufferArgSize,
+ sizeArgValue, ba) and
+ message =
+ "Buffer accesses offset " + sizeArgValue + " which is greater than the fixed size " +
+ bufferArgSize + " of the $@."
+ )
+ or
+ exists(int sizeArgUpperBound, int sizeMult, int bufferArgSize |
+ OOB::isSizeArgNotCheckedLessThanFixedBufferSize(bufferArg, sizeArg, bufferSource,
+ bufferArgSize, ba, sizeArgUpperBound, sizeMult) and
+ message =
+ "Buffer may access up to offset " + sizeArgUpperBound + "*" + sizeMult +
+ " which is greater than the fixed size " + bufferArgSize + " of the $@."
+ )
+ or
+ OOB::isSizeArgNotCheckedGreaterThanZero(bufferArg, sizeArg, bufferSource, ba) and
+ message = "Buffer access may be to a negative index in the buffer."
+ )
+select ba, message, bufferSource, "buffer"
diff --git a/c/cert/src/rules/ARR32-C/VariableLengthArraySizeNotInValidRange.md b/c/cert/src/rules/ARR32-C/VariableLengthArraySizeNotInValidRange.md
new file mode 100644
index 0000000000..d8554e2ef6
--- /dev/null
+++ b/c/cert/src/rules/ARR32-C/VariableLengthArraySizeNotInValidRange.md
@@ -0,0 +1,194 @@
+# ARR32-C: Ensure size arguments for variable length arrays are in a valid range
+
+This query implements the CERT-C rule ARR32-C:
+
+> Ensure size arguments for variable length arrays are in a valid range
+
+
+## Description
+
+Variable length arrays (VLAs), a conditionally supported language feature, are essentially the same as traditional C arrays except that they are declared with a size that is not a constant integer expression and can be declared only at block scope or function prototype scope and no linkage. When supported, a variable length array can be declared
+
+```cpp
+{ /* Block scope */
+ char vla[size];
+}
+
+```
+where the integer expression `size` and the declaration of `vla` are both evaluated at runtime. If the size argument supplied to a variable length array is not a positive integer value, the behavior is undefined. (See [undefined behavior 75](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_75).) Additionally, if the magnitude of the argument is excessive, the program may behave in an unexpected way. An attacker may be able to leverage this behavior to overwrite critical program data \[[Griffiths 2006](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Griffiths06)\]. The programmer must ensure that size arguments to variable length arrays, especially those derived from untrusted data, are in a valid range.
+
+Because variable length arrays are a conditionally supported feature of C11, their use in portable code should be guarded by testing the value of the macro `__STDC_NO_VLA__`. Implementations that do not support variable length arrays indicate it by setting `__STDC_NO_VLA__` to the integer constant 1.
+
+## Noncompliant Code Example
+
+In this noncompliant code example, a variable length array of size `size` is declared. The `size` is declared as `size_t` in compliance with [INT01-C. Use rsize_t or size_t for all integer values representing the size of an object](https://wiki.sei.cmu.edu/confluence/display/c/INT01-C.+Use+rsize_t+or+size_t+for+all+integer+values+representing+the+size+of+an+object).
+
+```cpp
+#include
+
+extern void do_work(int *array, size_t size);
+
+void func(size_t size) {
+ int vla[size];
+ do_work(vla, size);
+}
+
+```
+However, the value of `size` may be zero or excessive, potentially giving rise to a security [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability).
+
+## Compliant Solution
+
+This compliant solution ensures the `size` argument used to allocate `vla` is in a valid range (between 1 and a programmer-defined maximum); otherwise, it uses an algorithm that relies on dynamic memory allocation. The solution also avoids unsigned integer wrapping that, given a sufficiently large value of `size`, would cause `malloc` to allocate insufficient storage for the array.
+
+```cpp
+#include
+#include
+
+enum { MAX_ARRAY = 1024 };
+extern void do_work(int *array, size_t size);
+
+void func(size_t size) {
+ if (0 == size || SIZE_MAX / sizeof(int) < size) {
+ /* Handle error */
+ return;
+ }
+ if (size < MAX_ARRAY) {
+ int vla[size];
+ do_work(vla, size);
+ } else {
+ int *array = (int *)malloc(size * sizeof(int));
+ if (array == NULL) {
+ /* Handle error */
+ }
+ do_work(array, size);
+ free(array);
+ }
+}
+
+```
+
+## Noncompliant Code Example (sizeof)
+
+The following noncompliant code example defines `A` to be a variable length array and then uses the `sizeof` operator to compute its size at runtime. When the function is called with an argument greater than `SIZE_MAX / (N1 * sizeof (int))`, the runtime `sizeof` expression may wrap around, yielding a result that is smaller than the mathematical product `N1 * n2 * sizeof (int)`. The call to `malloc()`, when successful, will then allocate storage for fewer than `n2` elements of the array, causing one or more of the final `memset()` calls in the `for` loop to write past the end of that storage.
+
+```cpp
+#include
+#include
+
+enum { N1 = 4096 };
+
+void *func(size_t n2) {
+ typedef int A[n2][N1];
+
+ A *array = malloc(sizeof(A));
+ if (!array) {
+ /* Handle error */
+ return NULL;
+ }
+
+ for (size_t i = 0; i != n2; ++i) {
+ memset(array[i], 0, N1 * sizeof(int));
+ }
+
+ return array;
+}
+
+```
+Furthermore, this code also violates [ARR39-C. Do not add or subtract a scaled integer to a pointer](https://wiki.sei.cmu.edu/confluence/display/c/ARR39-C.+Do+not+add+or+subtract+a+scaled+integer+to+a+pointer), where `array` is a pointer to the two-dimensional array, where it should really be a pointer to the latter dimension instead. This means that the `memset() `call does out-of-bounds writes on all of its invocations except the first.
+
+## Compliant Solution (sizeof)
+
+This compliant solution prevents `sizeof` wrapping by detecting the condition before it occurs and avoiding the subsequent computation when the condition is detected. The code also uses an additional typedef to fix the type of `array` so that `memset()` never writes past the two-dimensional array.
+
+```cpp
+#include
+#include
+#include
+
+enum { N1 = 4096 };
+
+void *func(size_t n2) {
+ if (n2 > SIZE_MAX / (N1 * sizeof(int))) {
+ /* Prevent sizeof wrapping */
+ return NULL;
+ }
+
+ typedef int A1[N1];
+ typedef A1 A[n2];
+
+ A1 *array = (A1*) malloc(sizeof(A));
+
+ if (!array) {
+ /* Handle error */
+ return NULL;
+ }
+
+ for (size_t i = 0; i != n2; ++i) {
+ memset(array[i], 0, N1 * sizeof(int));
+ }
+ return array;
+}
+
+```
+**Implementation Details**
+
+**Microsoft**
+
+Variable length arrays are not supported by Microsoft compilers.
+
+## Risk Assessment
+
+Failure to properly specify the size of a variable length array may allow arbitrary code execution or result in stack exhaustion.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ARR32-C | High | Probable | High | P6 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
CodeSonar | 7.2p0 | ALLOC.SIZE.IOFLOWALLOC.SIZE.MULOFLOWMISC.MEM.SIZE.BAD | Integer Overflow of Allocation Size Multiplication Overflow of Allocation Size Unreasonable Size Argument |
Coverity | 2017.07 | REVERSE_NEGATIVE | Fully implemented |
Helix QAC | 2022.4 | C1051 | |
Klocwork | 2022.4 | MISRA.ARRAY.VAR_LENGTH.2012 | |
LDRA tool suite | 9.7.1 | 621 S | Enhanced enforcement |
Parasoft C/C++test | 2022.2 | CERT_C-ARR32-a | Ensure the size of the variable length array is in valid range |
PC-lint Plus | 1.4 | 9035 | Assistance provided |
Polyspace Bug Finder | R2023a | CERT C: Rule ARR32-C | Checks for: Memory allocation with tainted sizeemory allocation with tainted size, tainted size of variable length arrayainted size of variable length array. Rule fully covered. |
PRQA QA-C | 9.7 | 1051 | Partially implemented |
Cppcheck | 1.66 | negativeArraySize | Context sensitive analysis Will warn only if given size is negative |
TrustInSoft Analyzer | 1.38 | alloca_bounds | Exhaustively verified. |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ARR32-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-129 and ARR32-C**
+
+Intersection( CWE-188, EXP39-C) = Ø
+
+ARR32-C addresses specifying the size of a variable-length array (VLA). CWE-129 addresses invalid array indices, not array sizes.
+
+**CWE-758 and ARR32-C**
+
+Independent( INT34-C, INT36-C, MSC37-C, FLP32-C, EXP33-C, EXP30-C, ERR34-C, ARR32-C)
+
+CWE-758 = Union( ARR32-C, list) where list =
+
+* Undefined behavior that results from anything other than too large a VLA dimension.
+**CWE-119 and ARR32-C**
+* Intersection( CWE-119, ARR32-C) = Ø
+* ARR32-C is not about providing a valid buffer but reading/writing outside it. It is about providing an invalid buffer, or one that exhausts the stack.
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ARR32-C: Ensure size arguments for variable length arrays are in a valid range](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ARR32-C/VariableLengthArraySizeNotInValidRange.ql b/c/cert/src/rules/ARR32-C/VariableLengthArraySizeNotInValidRange.ql
new file mode 100644
index 0000000000..1356777e5f
--- /dev/null
+++ b/c/cert/src/rules/ARR32-C/VariableLengthArraySizeNotInValidRange.ql
@@ -0,0 +1,177 @@
+/**
+ * @id c/cert/variable-length-array-size-not-in-valid-range
+ * @name ARR32-C: Ensure size arguments for variable length arrays are in a valid range
+ * @description A variable-length array size that is zero, negative, overflowed, wrapped around, or
+ * excessively large may lead to undefined behaviour.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/arr32-c
+ * correctness
+ * security
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p6
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.Overflow
+import semmle.code.cpp.dataflow.TaintTracking
+
+/**
+ * Gets the maximum size (in bytes) a variable-length array
+ * should be to not be deemed excessively large persuant to this rule.
+ * This value has been arbitrarily chosen to be 2^16 - 1 bytes.
+ */
+private int maximumTotalVlaSize() { result = 65535 }
+
+/**
+ * Gets the base type of a pointer or array type. In the case of an array of
+ * arrays, the inner base type is returned.
+ *
+ * Copied from IncorrectPointerScalingCommon.qll.
+ */
+private Type baseType(Type t) {
+ (
+ exists(PointerType dt |
+ dt = t.getUnspecifiedType() and
+ result = dt.getBaseType().getUnspecifiedType()
+ )
+ or
+ exists(ArrayType at |
+ at = t.getUnspecifiedType() and
+ not at.getBaseType().getUnspecifiedType() instanceof ArrayType and
+ result = at.getBaseType().getUnspecifiedType()
+ )
+ or
+ exists(ArrayType at, ArrayType at2 |
+ at = t.getUnspecifiedType() and
+ at2 = at.getBaseType().getUnspecifiedType() and
+ result = baseType(at2)
+ )
+ ) and
+ // Make sure that the type has a size and that it isn't ambiguous.
+ strictcount(result.getSize()) = 1
+}
+
+/**
+ * The `SimpleRangeAnalysis` analysis over-zealously expands upper bounds of
+ * `SubExpr`s to account for potential wrapping even when no wrapping can occur.
+ *
+ * This class represents a `SubExpr` that is safe from wrapping.
+ */
+class SafeSubExprWithErroneouslyWrappedUpperBound extends SubExpr {
+ SafeSubExprWithErroneouslyWrappedUpperBound() {
+ lowerBound(this.getLeftOperand().getFullyConverted()) -
+ upperBound(this.getRightOperand().getFullyConverted()) >= 0 and
+ upperBound(this.getFullyConverted()) = exprMaxVal(this.getFullyConverted())
+ }
+
+ /**
+ * Gets the lower bound of the difference.
+ */
+ float getlowerBoundOfDifference() {
+ result =
+ lowerBound(this.getLeftOperand().getFullyConverted()) -
+ upperBound(this.getRightOperand().getFullyConverted())
+ }
+}
+
+/**
+ * Holds if `e` is an expression that is not in a valid range due to it
+ * being partially or fully derived from an overflowing arithmetic operation.
+ */
+predicate isExprTaintedByOverflowingExpr(Expr e) {
+ exists(InterestingOverflowingOperation bop |
+ // `bop` is not pre-checked to prevent overflow/wrapping
+ not bop.hasValidPreCheck() and
+ // and the destination is tainted by `bop`
+ TaintTracking::localExprTaint(bop, e.getAChild*()) and
+ // and there does not exist a post-wrapping-check before `e`
+ not exists(GuardCondition gc |
+ gc = bop.getAValidPostCheck() and
+ gc.controls(e.getBasicBlock(), _)
+ )
+ )
+}
+
+predicate getVlaSizeExprBounds(Expr e, float lower, float upper) {
+ lower = lowerBound(e) and
+ upper =
+ // upper is the smallest of either a `SubExpr` which flows to `e` and does
+ // not wrap, or the upper bound of `e` derived from the range-analysis library
+ min(float f |
+ f =
+ any(SafeSubExprWithErroneouslyWrappedUpperBound sub |
+ DataFlow::localExprFlow(sub, e)
+ |
+ sub.getlowerBoundOfDifference()
+ ) or
+ f = upperBound(e)
+ )
+}
+
+/**
+ * Holds if `e` is not bounded to a valid range, (0 .. maximumTotalVlaSize()], for
+ * a element count of an individual variable-length array dimension.
+ */
+predicate isVlaSizeExprOutOfRange(VlaDeclStmt vla, Expr e) {
+ vla.getVlaDimensionStmt(_).getDimensionExpr() = e and
+ exists(float lower, float upper |
+ getVlaSizeExprBounds(e.getFullyConverted(), lower, upper) and
+ (
+ lower <= 0
+ or
+ upper > maximumTotalVlaSize() / baseType(vla.getVariable().getType()).getSize()
+ )
+ )
+}
+
+/**
+ * Returns the upper bound of `e.getFullyConverted()`.
+ */
+float getVlaSizeExprUpperBound(Expr e) { getVlaSizeExprBounds(e.getFullyConverted(), _, result) }
+
+/**
+ * Returns the upper bound of `vla`'s dimension expression at `index`.
+ *
+ * If `index` does not exist, then the result is `1`.
+ */
+bindingset[index]
+private float getVlaSizeExprUpperBoundAtIndexOrOne(VlaDeclStmt vla, float index) {
+ if vla.getNumberOfVlaDimensionStmts() > index
+ then result = getVlaSizeExprUpperBound(vla.getVlaDimensionStmt(index).getDimensionExpr())
+ else result = 1
+}
+
+predicate vlaupper = getVlaSizeExprUpperBoundAtIndexOrOne/2;
+
+/**
+ * Gets the upper bound of the total size of `vla`.
+ */
+float getTotalVlaSizeUpperBound(VlaDeclStmt vla) {
+ result =
+ vlaupper(vla, 0) * vlaupper(vla, 1) * vlaupper(vla, 2) * vlaupper(vla, 3) * vlaupper(vla, 4) *
+ vlaupper(vla, 5) * vlaupper(vla, 6) * vlaupper(vla, 7) * vlaupper(vla, 8) * vlaupper(vla, 9)
+}
+
+from VlaDeclStmt vla, string message
+where
+ not isExcluded(vla, InvalidMemory2Package::variableLengthArraySizeNotInValidRangeQuery()) and
+ (
+ if isExprTaintedByOverflowingExpr(vla.getVlaDimensionStmt(_).getDimensionExpr())
+ then message = "Variable-length array size derives from an overflowing or wrapping expression."
+ else (
+ if isVlaSizeExprOutOfRange(vla, vla.getVlaDimensionStmt(_).getDimensionExpr())
+ then message = "Variable-length array dimension size may be in an invalid range."
+ else (
+ getTotalVlaSizeUpperBound(vla) > maximumTotalVlaSize() and
+ message = "Variable-length array total size may be excessively large."
+ )
+ )
+ )
+select vla, message
diff --git a/c/cert/src/rules/ARR36-C/DoNotRelatePointersThatDoNotReferToTheSameArray.md b/c/cert/src/rules/ARR36-C/DoNotRelatePointersThatDoNotReferToTheSameArray.md
new file mode 100644
index 0000000000..320eaa0c05
--- /dev/null
+++ b/c/cert/src/rules/ARR36-C/DoNotRelatePointersThatDoNotReferToTheSameArray.md
@@ -0,0 +1,105 @@
+# ARR36-C: Do not subtract two pointers that do not refer to the same array
+
+This query implements the CERT-C rule ARR36-C:
+
+> Do not subtract or compare two pointers that do not refer to the same array
+
+
+## Description
+
+When two pointers are subtracted, both must point to elements of the same array object or just one past the last element of the array object (C Standard, 6.5.6 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\]); the result is the difference of the subscripts of the two array elements. Otherwise, the operation is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). (See [undefined behavior 48](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_48).)
+
+Similarly, comparing pointers using the relational operators `<`, `<=`, `>=`, and `>` gives the positions of the pointers relative to each other. Subtracting or comparing pointers that do not refer to the same array is undefined behavior. (See [undefined behavior 48](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_48) and [undefined behavior 53](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_53).)
+
+Comparing pointers using the equality operators `==` and `!=` has well-defined semantics regardless of whether or not either of the pointers is null, points into the same object, or points one past the last element of an array object or function.
+
+## Noncompliant Code Example
+
+In this noncompliant code example, pointer subtraction is used to determine how many free elements are left in the `nums` array:
+
+```cpp
+#include
+
+enum { SIZE = 32 };
+
+void func(void) {
+ int nums[SIZE];
+ int end;
+ int *next_num_ptr = nums;
+ size_t free_elements;
+
+ /* Increment next_num_ptr as array fills */
+
+ free_elements = &end - next_num_ptr;
+}
+```
+This program incorrectly assumes that the `nums` array is adjacent to the `end` variable in memory. A compiler is permitted to insert padding bits between these two variables or even reorder them in memory.
+
+## Compliant Solution
+
+In this compliant solution, the number of free elements is computed by subtracting `next_num_ptr` from the address of the pointer past the `nums` array. While this pointer may not be dereferenced, it may be used in pointer arithmetic.
+
+```cpp
+#include
+enum { SIZE = 32 };
+
+void func(void) {
+ int nums[SIZE];
+ int *next_num_ptr = nums;
+ size_t free_elements;
+
+ /* Increment next_num_ptr as array fills */
+
+ free_elements = &(nums[SIZE]) - next_num_ptr;
+}
+```
+
+## Exceptions
+
+**ARR36-C-EX1:**Comparing two pointers to distinct members of the same `struct` object is allowed. Pointers to structure members declared later in the structure compare greater-than pointers to members declared earlier in the structure.
+
+## Risk Assessment
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ARR36-C | Medium | Probable | Medium | P8 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | pointer-subtraction | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-ARR36 | Can detect operations on pointers that are unrelated |
CodeSonar | 7.2p0 | LANG.STRUCT.CUP LANG.STRUCT.SUP | Comparison of Unrelated Pointers Subtraction of Unrelated Pointers |
Coverity | 2017.07 | MISRA C 2004 17.2 MISRA C 2004 17.3 MISRA C 2012 18.2 MISRA C 2012 18.3 | Implemented |
Helix QAC | 2022.4 | C0487, C0513 DF2668, DF2669, DF2761, DF2762, DF2763, DF2766, DF2767, DF2768, DF2771, DF2772, DF2773 | |
Klocwork | 2022.4 | MISRA.PTR.ARITH | |
LDRA tool suite | 9.7.1 | 437 S, 438 S | Fully implemented |
Parasoft C/C++test | 2022.2 | CERT_C-ARR36-aCERT_C-ARR36-b | Do not subtract two pointers that do not address elements of the same array Do not compare two unrelated pointers |
Polyspace Bug Finder | R2023a | CERT C: Rule ARR36-C | Checks for subtraction or comparison between pointers to different arrays (rule partially covered) |
PRQA QA-C | 9.7 | 0487, 0513, 2668, 2669, 2761, 2762, 2763, 2766, 2767, 2768, 2771, 2772, 2773 | Fully implemented |
PVS-Studio | 7.23 | V736 , V782 | |
RuleChecker | 22.04 | pointer-subtraction | Partially checked |
TrustInSoft Analyzer | 1.38 | differing_blocks | Exhaustively verified (see the compliant and the non-compliant example ). |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ARR36-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-469 and ARR36-C**
+
+CWE-469 = Subset(ARR36-C)
+
+ARR36-C = Union(CWE-469, list) where list =
+
+* Pointer comparisons using the relational operators `<`, `<=`, `>=`, and `>`, where the pointers do not refer to the same array
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ARR36-C: Do not subtract or compare two pointers that do not refer to the same array](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ARR36-C/DoNotRelatePointersThatDoNotReferToTheSameArray.ql b/c/cert/src/rules/ARR36-C/DoNotRelatePointersThatDoNotReferToTheSameArray.ql
new file mode 100644
index 0000000000..e42437042f
--- /dev/null
+++ b/c/cert/src/rules/ARR36-C/DoNotRelatePointersThatDoNotReferToTheSameArray.ql
@@ -0,0 +1,28 @@
+/**
+ * @id c/cert/do-not-relate-pointers-that-do-not-refer-to-the-same-array
+ * @name ARR36-C: Do not subtract two pointers that do not refer to the same array
+ * @description Comparison using the >, >=, <, and <= operators between pointers referring to
+ * differing arrays results in undefined behavior.
+ * @kind path-problem
+ * @precision high
+ * @problem.severity warning
+ * @tags external/cert/id/arr36-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.rules.donotuserelationaloperatorswithdifferingarrays.DoNotUseRelationalOperatorsWithDifferingArrays
+
+class DoNotRelatePointersThatDoNotReferToTheSameArrayQuery extends DoNotUseRelationalOperatorsWithDifferingArraysSharedQuery
+{
+ DoNotRelatePointersThatDoNotReferToTheSameArrayQuery() {
+ this = Memory2Package::doNotRelatePointersThatDoNotReferToTheSameArrayQuery()
+ }
+}
diff --git a/c/cert/src/rules/ARR36-C/DoNotSubtractPointersThatDoNotReferToTheSameArray.md b/c/cert/src/rules/ARR36-C/DoNotSubtractPointersThatDoNotReferToTheSameArray.md
new file mode 100644
index 0000000000..320eaa0c05
--- /dev/null
+++ b/c/cert/src/rules/ARR36-C/DoNotSubtractPointersThatDoNotReferToTheSameArray.md
@@ -0,0 +1,105 @@
+# ARR36-C: Do not subtract two pointers that do not refer to the same array
+
+This query implements the CERT-C rule ARR36-C:
+
+> Do not subtract or compare two pointers that do not refer to the same array
+
+
+## Description
+
+When two pointers are subtracted, both must point to elements of the same array object or just one past the last element of the array object (C Standard, 6.5.6 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\]); the result is the difference of the subscripts of the two array elements. Otherwise, the operation is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). (See [undefined behavior 48](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_48).)
+
+Similarly, comparing pointers using the relational operators `<`, `<=`, `>=`, and `>` gives the positions of the pointers relative to each other. Subtracting or comparing pointers that do not refer to the same array is undefined behavior. (See [undefined behavior 48](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_48) and [undefined behavior 53](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_53).)
+
+Comparing pointers using the equality operators `==` and `!=` has well-defined semantics regardless of whether or not either of the pointers is null, points into the same object, or points one past the last element of an array object or function.
+
+## Noncompliant Code Example
+
+In this noncompliant code example, pointer subtraction is used to determine how many free elements are left in the `nums` array:
+
+```cpp
+#include
+
+enum { SIZE = 32 };
+
+void func(void) {
+ int nums[SIZE];
+ int end;
+ int *next_num_ptr = nums;
+ size_t free_elements;
+
+ /* Increment next_num_ptr as array fills */
+
+ free_elements = &end - next_num_ptr;
+}
+```
+This program incorrectly assumes that the `nums` array is adjacent to the `end` variable in memory. A compiler is permitted to insert padding bits between these two variables or even reorder them in memory.
+
+## Compliant Solution
+
+In this compliant solution, the number of free elements is computed by subtracting `next_num_ptr` from the address of the pointer past the `nums` array. While this pointer may not be dereferenced, it may be used in pointer arithmetic.
+
+```cpp
+#include
+enum { SIZE = 32 };
+
+void func(void) {
+ int nums[SIZE];
+ int *next_num_ptr = nums;
+ size_t free_elements;
+
+ /* Increment next_num_ptr as array fills */
+
+ free_elements = &(nums[SIZE]) - next_num_ptr;
+}
+```
+
+## Exceptions
+
+**ARR36-C-EX1:**Comparing two pointers to distinct members of the same `struct` object is allowed. Pointers to structure members declared later in the structure compare greater-than pointers to members declared earlier in the structure.
+
+## Risk Assessment
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ARR36-C | Medium | Probable | Medium | P8 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | pointer-subtraction | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-ARR36 | Can detect operations on pointers that are unrelated |
CodeSonar | 7.2p0 | LANG.STRUCT.CUP LANG.STRUCT.SUP | Comparison of Unrelated Pointers Subtraction of Unrelated Pointers |
Coverity | 2017.07 | MISRA C 2004 17.2 MISRA C 2004 17.3 MISRA C 2012 18.2 MISRA C 2012 18.3 | Implemented |
Helix QAC | 2022.4 | C0487, C0513 DF2668, DF2669, DF2761, DF2762, DF2763, DF2766, DF2767, DF2768, DF2771, DF2772, DF2773 | |
Klocwork | 2022.4 | MISRA.PTR.ARITH | |
LDRA tool suite | 9.7.1 | 437 S, 438 S | Fully implemented |
Parasoft C/C++test | 2022.2 | CERT_C-ARR36-aCERT_C-ARR36-b | Do not subtract two pointers that do not address elements of the same array Do not compare two unrelated pointers |
Polyspace Bug Finder | R2023a | CERT C: Rule ARR36-C | Checks for subtraction or comparison between pointers to different arrays (rule partially covered) |
PRQA QA-C | 9.7 | 0487, 0513, 2668, 2669, 2761, 2762, 2763, 2766, 2767, 2768, 2771, 2772, 2773 | Fully implemented |
PVS-Studio | 7.23 | V736 , V782 | |
RuleChecker | 22.04 | pointer-subtraction | Partially checked |
TrustInSoft Analyzer | 1.38 | differing_blocks | Exhaustively verified (see the compliant and the non-compliant example ). |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ARR36-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-469 and ARR36-C**
+
+CWE-469 = Subset(ARR36-C)
+
+ARR36-C = Union(CWE-469, list) where list =
+
+* Pointer comparisons using the relational operators `<`, `<=`, `>=`, and `>`, where the pointers do not refer to the same array
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ARR36-C: Do not subtract or compare two pointers that do not refer to the same array](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ARR36-C/DoNotSubtractPointersThatDoNotReferToTheSameArray.ql b/c/cert/src/rules/ARR36-C/DoNotSubtractPointersThatDoNotReferToTheSameArray.ql
new file mode 100644
index 0000000000..a9e53e68b7
--- /dev/null
+++ b/c/cert/src/rules/ARR36-C/DoNotSubtractPointersThatDoNotReferToTheSameArray.ql
@@ -0,0 +1,28 @@
+/**
+ * @id c/cert/do-not-subtract-pointers-that-do-not-refer-to-the-same-array
+ * @name ARR36-C: Do not subtract two pointers that do not refer to the same array
+ * @description Subtraction between pointers referring to differing arrays results in undefined
+ * behavior.
+ * @kind path-problem
+ * @precision high
+ * @problem.severity warning
+ * @tags external/cert/id/arr36-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.rules.donotsubtractpointersaddressingdifferentarrays.DoNotSubtractPointersAddressingDifferentArrays
+
+class DoNotSubtractPointersThatDoNotReferToTheSameArrayQuery extends DoNotSubtractPointersAddressingDifferentArraysSharedQuery
+{
+ DoNotSubtractPointersThatDoNotReferToTheSameArrayQuery() {
+ this = Memory2Package::doNotSubtractPointersThatDoNotReferToTheSameArrayQuery()
+ }
+}
diff --git a/c/cert/src/rules/ARR37-C/DoNotUsePointerArithmeticOnNonArrayObjectPointers.md b/c/cert/src/rules/ARR37-C/DoNotUsePointerArithmeticOnNonArrayObjectPointers.md
new file mode 100644
index 0000000000..7772bf4d3d
--- /dev/null
+++ b/c/cert/src/rules/ARR37-C/DoNotUsePointerArithmeticOnNonArrayObjectPointers.md
@@ -0,0 +1,148 @@
+# ARR37-C: Do not add or subtract an integer to a pointer to a non-array object
+
+This query implements the CERT-C rule ARR37-C:
+
+> Do not add or subtract an integer to a pointer to a non-array object
+
+
+## Description
+
+Pointer arithmetic must be performed only on pointers that reference elements of array objects.
+
+The C Standard, 6.5.6 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], states the following about pointer arithmetic:
+
+> When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression.
+
+
+## Noncompliant Code Example
+
+This noncompliant code example attempts to access structure members using pointer arithmetic. This practice is dangerous because structure members are not guaranteed to be contiguous.
+
+```cpp
+struct numbers {
+ short num_a, num_b, num_c;
+};
+
+int sum_numbers(const struct numbers *numb){
+ int total = 0;
+ const short *numb_ptr;
+
+ for (numb_ptr = &numb->num_a;
+ numb_ptr <= &numb->num_c;
+ numb_ptr++) {
+ total += *(numb_ptr);
+ }
+
+ return total;
+}
+
+int main(void) {
+ struct numbers my_numbers = { 1, 2, 3 };
+ sum_numbers(&my_numbers);
+ return 0;
+}
+
+```
+
+## Compliant Solution
+
+It is possible to use the `->` operator to dereference each structure member:
+
+```cpp
+total = numb->num_a + numb->num_b + numb->num_c;
+
+```
+However, this solution results in code that is hard to write and hard to maintain (especially if there are many more structure members), which is exactly what the author of the noncompliant code example was likely trying to avoid.
+
+## Compliant Solution
+
+A better solution is to define the structure to contain an array member to store the numbers in an array rather than a structure, as in this compliant solution:
+
+```cpp
+#include
+
+struct numbers {
+ short a[3];
+};
+
+int sum_numbers(const short *numb, size_t dim) {
+ int total = 0;
+ for (size_t i = 0; i < dim; ++i) {
+ total += numb[i];
+ }
+
+ return total;
+}
+
+int main(void) {
+ struct numbers my_numbers = { .a[0]= 1, .a[1]= 2, .a[2]= 3};
+ sum_numbers(
+ my_numbers.a,
+ sizeof(my_numbers.a)/sizeof(my_numbers.a[0])
+ );
+ return 0;
+}
+
+```
+Array elements are guaranteed to be contiguous in memory, so this solution is completely portable.
+
+## Exceptions
+
+** ARR37-C-EX1:** Any non-array object in memory can be considered an array consisting of one element. Adding one to a pointer for such an object yields a pointer one element past the array, and subtracting one from that pointer yields the original pointer. This allows for code such as the following:
+
+```cpp
+#include
+#include
+
+struct s {
+ char *c_str;
+ /* Other members */
+};
+
+struct s *create_s(const char *c_str) {
+ struct s *ret;
+ size_t len = strlen(c_str) + 1;
+
+ ret = (struct s *)malloc(sizeof(struct s) + len);
+ if (ret != NULL) {
+ ret->c_str = (char *)(ret + 1);
+ memcpy(ret + 1, c_str, len);
+ }
+ return ret;
+}
+```
+A more general and safer solution to this problem is to use a flexible array member that guarantees the array that follows the structure is properly aligned by inserting padding, if necessary, between it and the member that immediately precedes it.
+
+## Risk Assessment
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ARR37-C | Medium | Probable | Medium | P8 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | | Supported indirectly via MISRA C:2004 Rule 17.4. |
Axivion Bauhaus Suite | 7.2.0 | CertC-ARR37 | Fully implemented |
CodeSonar | 7.2p0 | LANG.MEM.BO LANG.MEM.BU LANG.STRUCT.PARITH LANG.STRUCT.PBB LANG.STRUCT.PPE LANG.MEM.TBA LANG.MEM.TO LANG.MEM.TU | Buffer Overrun Buffer Underrun Pointer Arithmetic Pointer Before Beginning of Object Pointer Past End of Object Tainted Buffer Access Type Overrun Type Underrun |
Compass/ROSE | | | |
Coverity | 2017.07 | ARRAY_VS_SINGLETON | Implemented |
Helix QAC | 2022.4 | DF2930, DF2931, DF2932, DF2933 C++3705, C++3706, C++3707 | |
Klocwork | 2022.4 | MISRA.PTR.ARITH.2012 | |
LDRA tool suite | 9.7.1 | 567 S | Partially implemented |
Parasoft C/C++test | 2022.2 | CERT_C-ARR37-a | Pointer arithmetic shall not be applied to pointers that address variables of non-array type |
PC-lint Plus | 1.4 | 2662 | Partially supported |
Polyspace Bug Finder | R2023a | CERT C: Rule ARR37-C | Checks for invalid assumptions about memory organization (rule partially covered) |
PRQA QA-C | 9.7 | 2930, 2931, 2932, 2933 | |
PRQA QA-C++ | 4.4 | 2930, 2931, 2932, 2933, 3705, 3706, 3707 | |
RuleChecker | 22.04 | | Supported indirectly via MISRA C:2004 Rule 17.4. |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ARR37-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+ Taxonomy | Taxonomy item | Relationship |
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ARR37-C: Do not add or subtract an integer to a pointer to a non-array object](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ARR37-C/DoNotUsePointerArithmeticOnNonArrayObjectPointers.ql b/c/cert/src/rules/ARR37-C/DoNotUsePointerArithmeticOnNonArrayObjectPointers.ql
new file mode 100644
index 0000000000..635d9d5c03
--- /dev/null
+++ b/c/cert/src/rules/ARR37-C/DoNotUsePointerArithmeticOnNonArrayObjectPointers.ql
@@ -0,0 +1,117 @@
+/**
+ * @id c/cert/do-not-use-pointer-arithmetic-on-non-array-object-pointers
+ * @name ARR37-C: Do not add or subtract an integer to a pointer to a non-array object
+ * @description A pair of elements that are not elements in the same array are not guaranteed to be
+ * contiguous in memory and therefore should not be addressed using pointer arithmetic.
+ * @kind path-problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/arr37-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import semmle.code.cpp.dataflow.DataFlow
+import NonArrayPointerToArrayIndexingExprFlow::PathGraph
+
+/**
+ * A data-flow configuration that tracks flow from an `AddressOfExpr` of a variable
+ * of `PointerType` that is not also an `ArrayType` to a `PointerArithmeticOrArrayExpr`
+ */
+module NonArrayPointerToArrayIndexingExprConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ exists(AddressOfExpr ao, Type t |
+ source.asExpr() = ao and
+ not ao.getOperand() instanceof ArrayExpr and
+ not ao.getOperand() instanceof PointerDereferenceExpr and
+ t = ao.getOperand().getType() and
+ not t instanceof PointerType and
+ not t instanceof ArrayType and
+ not t.(PointerType).getBaseType() instanceof ArrayType
+ )
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(PointerArithmeticOrArrayExpr ae |
+ sink.asExpr() = ae.getPointerOperand() and
+ not sink.asExpr() instanceof Literal and
+ not ae.isNonPointerOperandZero()
+ )
+ }
+
+ predicate isBarrierOut(DataFlow::Node node) {
+ // the default interprocedural data-flow model flows through any field or array assignment
+ // expressions to the qualifier (array base, pointer dereferenced, or qualifier) instead of the
+ // individual element or field that the assignment modifies. this default behaviour causes
+ // false positives for future accesses of any element of that object, so we remove the edges
+ // between those assignments from the graph with `isBarrierOut`.
+ exists(AssignExpr a |
+ node.asExpr() = a.getRValue() and
+ (
+ a.getLValue() instanceof ArrayExpr or
+ a.getLValue() instanceof PointerDereferenceExpr or
+ a.getLValue() instanceof FieldAccess
+ )
+ )
+ or
+ // ignore AddressOfExpr output e.g. call(&s1)
+ node.asDefiningArgument() instanceof AddressOfExpr
+ }
+}
+
+module NonArrayPointerToArrayIndexingExprFlow =
+ DataFlow::Global;
+
+class PointerArithmeticOrArrayExpr extends Expr {
+ Expr operand;
+
+ PointerArithmeticOrArrayExpr() {
+ operand = this.(ArrayExpr).getArrayBase()
+ or
+ operand = this.(ArrayExpr).getArrayOffset()
+ or
+ operand = this.(PointerAddExpr).getAnOperand()
+ or
+ operand = this.(PointerSubExpr).getAnOperand()
+ or
+ operand = this.(Operation).getAnOperand() and
+ operand.getUnderlyingType() instanceof PointerType and
+ (
+ this instanceof PostfixCrementOperation
+ or
+ this instanceof PrefixIncrExpr
+ or
+ this instanceof PrefixDecrExpr
+ )
+ }
+
+ /**
+ * Gets the operands of this expression. If the expression is an
+ * `ArrayExpr`, the results are the array base and offset `Expr`s.
+ */
+ Expr getPointerOperand() {
+ result = operand or
+ result = this.(PointerArithmeticOrArrayExpr).getPointerOperand()
+ }
+
+ /**
+ * Holds if there exists an operand that is a `Literal` with a value of `0`.
+ */
+ predicate isNonPointerOperandZero() { operand.(Literal).getValue().toInt() = 0 }
+}
+
+from
+ NonArrayPointerToArrayIndexingExprFlow::PathNode source,
+ NonArrayPointerToArrayIndexingExprFlow::PathNode sink
+where
+ not isExcluded(sink.getNode().asExpr(),
+ InvalidMemory2Package::doNotUsePointerArithmeticOnNonArrayObjectPointersQuery()) and
+ NonArrayPointerToArrayIndexingExprFlow::flowPath(source, sink)
+select sink, source, sink, "Pointer arithmetic on non-array object pointer."
diff --git a/c/cert/src/rules/ARR38-C/LibraryFunctionArgumentOutOfBounds.md b/c/cert/src/rules/ARR38-C/LibraryFunctionArgumentOutOfBounds.md
new file mode 100644
index 0000000000..c3306036d2
--- /dev/null
+++ b/c/cert/src/rules/ARR38-C/LibraryFunctionArgumentOutOfBounds.md
@@ -0,0 +1,486 @@
+# ARR38-C: Guarantee that library functions do not form invalid pointers
+
+This query implements the CERT-C rule ARR38-C:
+
+> Guarantee that library functions do not form invalid pointers
+
+
+## Description
+
+C library functions that make changes to arrays or objects take at least two arguments: a pointer to the array or object and an integer indicating the number of elements or bytes to be manipulated. For the purposes of this rule, the element count of a pointer is the size of the object to which it points, expressed by the number of elements that are valid to access. Supplying arguments to such a function might cause the function to form a pointer that does not point into or just past the end of the object, resulting in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior).
+
+Annex J of the C Standard \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO%2FIEC9899-2011)\] states that it is undefined behavior if the "pointer passed to a library function array parameter does not have a value such that all address computations and object accesses are valid." (See [undefined behavior 109](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_109).)
+
+In the following code,
+
+```cpp
+int arr[5];
+int *p = arr;
+
+unsigned char *p2 = (unsigned char *)arr;
+unsigned char *p3 = arr + 2;
+void *p4 = arr;
+```
+the element count of the pointer `p` is `sizeof(arr) / sizeof(arr[0])`, that is, `5`. The element count of the pointer `p2` is `sizeof(arr)`, that is, `20`, on implementations where `sizeof(int) == 4`. The element count of the pointer `p3` is `12` on implementations where `sizeof(int) == 4`, because `p3` points two elements past the start of the array `arr`. The element count of `p4` is treated as though it were `unsigned char *` instead of `void *`, so it is the same as `p2`.
+
+## Pointer + Integer
+
+The following standard library functions take a pointer argument and a size argument, with the constraint that the pointer must point to a valid memory object of at least the number of elements indicated by the size argument.
+
+ fgets() | fgetws() | mbstowcs() 1 | wcstombs() 1 |
mbrtoc16() 2 | mbrtoc32() 2 | mbsrtowcs() 1 | wcsrtombs() 1 |
mbtowc() 2 | mbrtowc() 2 | mblen() | mbrlen() |
memchr() | wmemchr() | memset() | wmemset() |
strftime() | wcsftime() | strxfrm()1 | wcsxfrm()1 |
strncat()2 | wcsncat()2 | snprintf() | vsnprintf() |
swprintf() | vswprintf() | setvbuf() | tmpnam_s() |
snprintf_s() | sprintf_s() | vsnprintf_s() | vsprintf_s() |
gets_s() | getenv_s() | wctomb_s() | mbstowcs_s()3 |
wcstombs_s()3 | memcpy_s()3 | memmove_s()3 | strncpy_s()3 |
strncat_s()3 | strtok_s()2 | strerror_s() | strnlen_s() |
asctime_s() | ctime_s() | snwprintf_s() | swprintf_s() |
vsnwprintf_s() | vswprintf_s() | wcsncpy_s()3 | wmemcpy_s()3 |
wmemmove_s()3 | wcsncat_s()3 | wcstok_s()2 | wcsnlen_s() |
wcrtomb_s() | mbsrtowcs_s()3 | wcsrtombs_s()3 | memset_s()4 |
+1 Takes two pointers and an integer, but the integer specifies the element count only of the output buffer, not of the input buffer.2 Takes two pointers and an integer, but the integer specifies the element count only of the input buffer, not of the output buffer.3 Takes two pointers and two integers; each integer corresponds to the element count of one of the pointers.4 Takes a pointer and two size-related integers; the first size-related integer parameter specifies the number of bytes available in the buffer; the second size-related integer parameter specifies the number of bytes to write within the buffer.
+
+
+For calls that take a pointer and an integer size, the given size should not be greater than the element count of the pointer.
+
+** Noncompliant Code Example (Element Count)**
+
+In this noncompliant code example, the incorrect element count is used in a call to `wmemcpy()`. The `sizeof` operator returns the size expressed in bytes, but `wmemcpy()` uses an element count based on `wchar_t *`.
+
+```cpp
+#include
+#include
+
+static const char str[] = "Hello world";
+static const wchar_t w_str[] = L"Hello world";
+void func(void) {
+ char buffer[32];
+ wchar_t w_buffer[32];
+ memcpy(buffer, str, sizeof(str)); /* Compliant */
+ wmemcpy(w_buffer, w_str, sizeof(w_str)); /* Noncompliant */
+}
+```
+**Compliant Solution (Element Count)**
+
+When using functions that operate on pointed-to regions, programmers must always express the integer size in terms of the element count expected by the function. For example, `memcpy()` expects the element count expressed in terms of `void *`, but `wmemcpy()` expects the element count expressed in terms of `wchar_t *`. Instead of the `sizeof` operator, functions that return the number of elements in the string are called, which matches the expected element count for the copy functions. In the case of this compliant solution, where the argument is an array `A` of type `T`, the expression `sizeof(A) / sizeof(T)`, or equivalently `sizeof(A) / sizeof(*A)`, can be used to compute the number of elements in the array.
+
+```cpp
+#include
+#include
+
+static const char str[] = "Hello world";
+static const wchar_t w_str[] = L"Hello world";
+void func(void) {
+ char buffer[32];
+ wchar_t w_buffer[32];
+ memcpy(buffer, str, strlen(str) + 1);
+ wmemcpy(w_buffer, w_str, wcslen(w_str) + 1);
+}
+```
+**Noncompliant Code Example (Pointer + Integer)**
+
+This noncompliant code example assigns a value greater than the number of bytes of available memory to `n`, which is then passed to `memset()`:
+
+```cpp
+#include
+#include
+
+void f1(size_t nchars) {
+ char *p = (char *)malloc(nchars);
+ /* ... */
+ const size_t n = nchars + 1;
+ /* ... */
+ memset(p, 0, n);
+}
+
+```
+**Compliant Solution (Pointer + Integer)**
+
+This compliant solution ensures that the value of `n` is not greater than the number of bytes of the dynamic memory pointed to by the pointer `p`:
+
+```cpp
+#include
+#include
+
+void f1(size_t nchars) {
+ char *p = (char *)malloc(nchars);
+ /* ... */
+ const size_t n = nchars;
+ /* ... */
+ memset(p, 0, n);
+}
+
+```
+**Noncompliant Code Example (Pointer + Integer)**
+
+In this noncompliant code example, the element count of the array `a` is `ARR_SIZE` elements. Because `memset()` expects a byte count, the size of the array is scaled incorrectly by `sizeof(int)` instead of `sizeof(long)`, which can form an invalid pointer on architectures where `sizeof(int) != sizeof(long)`.
+
+```cpp
+#include
+
+void f2(void) {
+ const size_t ARR_SIZE = 4;
+ long a[ARR_SIZE];
+ const size_t n = sizeof(int) * ARR_SIZE;
+ void *p = a;
+
+ memset(p, 0, n);
+}
+
+```
+**Compliant Solution (Pointer + Integer)**
+
+In this compliant solution, the element count required by `memset()` is properly calculated without resorting to scaling:
+
+```cpp
+#include
+
+void f2(void) {
+ const size_t ARR_SIZE = 4;
+ long a[ARR_SIZE];
+ const size_t n = sizeof(a);
+ void *p = a;
+
+ memset(p, 0, n);
+}
+
+```
+
+## Two Pointers + One Integer
+
+The following standard library functions take two pointer arguments and a size argument, with the constraint that both pointers must point to valid memory objects of at least the number of elements indicated by the size argument.
+
+ memcpy() | wmemcpy() | memmove() | wmemmove() |
strncpy() | wcsncpy() | memcmp() | wmemcmp() |
strncmp() | wcsncmp() | strcpy_s() | wcscpy_s() |
strcat_s() | wcscat_s() | | |
+For calls that take two pointers and an integer size, the given size should not be greater than the element count of either pointer.
+
+
+**Noncompliant Code Example (Two Pointers + One Integer)**
+
+In this noncompliant code example, the value of `n` is incorrectly computed, allowing a read past the end of the object referenced by `q`:
+
+```cpp
+#include
+
+void f4() {
+ char p[40];
+ const char *q = "Too short";
+ size_t n = sizeof(p);
+ memcpy(p, q, n);
+}
+```
+**Compliant Solution (Two Pointers + One Integer)**
+
+This compliant solution ensures that `n` is equal to the size of the character array:
+
+```cpp
+#include
+
+void f4() {
+ char p[40];
+ const char *q = "Too short";
+ size_t n = sizeof(p) < strlen(q) + 1 ? sizeof(p) : strlen(q) + 1;
+ memcpy(p, q, n);
+}
+```
+
+## One Pointer + Two Integers
+
+The following standard library functions take a pointer argument and two size arguments, with the constraint that the pointer must point to a valid memory object containing at least as many bytes as the product of the two size arguments.
+
+ bsearch() | bsearch_s() | qsort() | qsort_s() |
fread() | fwrite() | | |
+For calls that take a pointer and two integers, one integer represents the number of bytes required for an individual object, and a second integer represents the number of elements in the array. The resulting product of the two integers should not be greater than the element count of the pointer were it expressed as an `unsigned char *`.
+
+
+**Noncompliant Code Example (One Pointer + Two Integers)**
+
+This noncompliant code example allocates a variable number of objects of type `struct obj`. The function checks that `num_objs` is small enough to prevent wrapping, in compliance with [INT30-C. Ensure that unsigned integer operations do not wrap](https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap). The size of `struct obj` is assumed to be 16 bytes to account for padding to achieve the assumed alignment of `long long`. However, the padding typically depends on the target architecture, so this object size may be incorrect, resulting in an incorrect element count.
+
+```cpp
+#include
+#include
+
+struct obj {
+ char c;
+ long long i;
+};
+
+void func(FILE *f, struct obj *objs, size_t num_objs) {
+ const size_t obj_size = 16;
+ if (num_objs > (SIZE_MAX / obj_size) ||
+ num_objs != fwrite(objs, obj_size, num_objs, f)) {
+ /* Handle error */
+ }
+}
+```
+**Compliant Solution (One Pointer + Two Integers)**
+
+This compliant solution uses the `sizeof` operator to correctly provide the object size and `num_objs` to provide the element count:
+
+```cpp
+#include
+#include
+
+struct obj {
+ char c;
+ long long i;
+};
+
+void func(FILE *f, struct obj *objs, size_t num_objs) {
+ const size_t obj_size = sizeof *objs;
+ if (num_objs > (SIZE_MAX / obj_size) ||
+ num_objs != fwrite(objs, obj_size, num_objs, f)) {
+ /* Handle error */
+ }
+}
+```
+**Noncompliant Code Example (One Pointer + Two Integers)**
+
+In this noncompliant code example, the function `f()` calls `fread()` to read `nitems` of type `wchar_t`, each `size` bytes in size, into an array of `BUFFER_SIZE` elements, `wbuf`. However, the expression used to compute the value of `nitems` fails to account for the fact that, unlike the size of `char`, the size of `wchar_t` may be greater than 1. Consequently, `fread()` could attempt to form pointers past the end of `wbuf` and use them to assign values to nonexistent elements of the array. Such an attempt is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). (See [undefined behavior 109](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_109).) A likely consequence of this undefined behavior is a buffer overflow. For a discussion of this programming error in the Common Weakness Enumeration database, see [CWE-121](http://cwe.mitre.org/data/definitions/121.html), "Stack-based Buffer Overflow," and [CWE-805](http://cwe.mitre.org/data/definitions/805.html), "Buffer Access with Incorrect Length Value."
+
+```cpp
+#include
+#include
+
+void f(FILE *file) {
+ enum { BUFFER_SIZE = 1024 };
+ wchar_t wbuf[BUFFER_SIZE];
+
+ const size_t size = sizeof(*wbuf);
+ const size_t nitems = sizeof(wbuf);
+
+ size_t nread = fread(wbuf, size, nitems, file);
+ /* ... */
+}
+
+```
+**Compliant Solution (One Pointer + Two Integers)**
+
+This compliant solution correctly computes the maximum number of items for `fread()` to read from the file:
+
+```cpp
+#include
+#include
+
+void f(FILE *file) {
+ enum { BUFFER_SIZE = 1024 };
+ wchar_t wbuf[BUFFER_SIZE];
+
+ const size_t size = sizeof(*wbuf);
+ const size_t nitems = sizeof(wbuf) / size;
+
+ size_t nread = fread(wbuf, size, nitems, file);
+ /* ... */
+}
+```
+**Noncompliant Code Example (Heartbleed)**
+
+CERT vulnerability [720951](http://www.kb.cert.org/vuls/id/720951) describes a vulnerability in OpenSSL versions 1.0.1 through 1.0.1f, popularly known as "Heartbleed." This vulnerability allows an attacker to steal information that under normal conditions would be protected by Secure Socket Layer/Transport Layer Security (SSL/TLS) encryption.
+
+Despite the seriousness of the vulnerability, Heartbleed is the result of a common programming error and an apparent lack of awareness of secure coding principles. Following is the vulnerable code:
+
+```cpp
+int dtls1_process_heartbeat(SSL *s) {
+ unsigned char *p = &s->s3->rrec.data[0], *pl;
+ unsigned short hbtype;
+ unsigned int payload;
+ unsigned int padding = 16; /* Use minimum padding */
+
+ /* Read type and payload length first */
+ hbtype = *p++;
+ n2s(p, payload);
+ pl = p;
+
+ /* ... More code ... */
+
+ if (hbtype == TLS1_HB_REQUEST) {
+ unsigned char *buffer, *bp;
+ int r;
+
+ /*
+ * Allocate memory for the response; size is 1 byte
+ * message type, plus 2 bytes payload length, plus
+ * payload, plus padding.
+ */
+ buffer = OPENSSL_malloc(1 + 2 + payload + padding);
+ bp = buffer;
+
+ /* Enter response type, length, and copy payload */
+ *bp++ = TLS1_HB_RESPONSE;
+ s2n(payload, bp);
+ memcpy(bp, pl, payload);
+
+ /* ... More code ... */
+ }
+ /* ... More code ... */
+}
+```
+This code processes a "heartbeat" packet from a client. As specified in [RFC 6520](https://tools.ietf.org/html/rfc6520), when the program receives a heartbeat packet, it must echo the packet's data back to the client. In addition to the data, the packet contains a length field that conventionally indicates the number of bytes in the packet data, but there is nothing to prevent a malicious packet from lying about its data length.
+
+The `p` pointer, along with `payload` and `p1`, contains data from a packet. The code allocates a `buffer` sufficient to contain `payload` bytes, with some overhead, then copies `payload` bytes starting at `p1` into this buffer and sends it to the client. Notably absent from this code are any checks that the payload integer variable extracted from the heartbeat packet corresponds to the size of the packet data. Because the client can specify an arbitrary value of `payload`, an attacker can cause the server to read and return the contents of memory beyond the end of the packet data, which violates [INT04-C. Enforce limits on integer values originating from tainted sources](https://wiki.sei.cmu.edu/confluence/display/c/INT04-C.+Enforce+limits+on+integer+values+originating+from+tainted+sources). The resulting call to `memcpy()` can then copy the contents of memory past the end of the packet data and the packet itself, potentially exposing sensitive data to the attacker. This call to `memcpy()` violates [ARR38-C. Guarantee that library functions do not form invalid pointers](https://wiki.sei.cmu.edu/confluence/display/c/ARR38-C.+Guarantee+that+library+functions+do+not+form+invalid+pointers). A version of ARR38-C also appears in [ISO/IEC TS 17961:2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IECTS17961), "Forming invalid pointers by library functions \[libptr\]." This rule would require a conforming analyzer to diagnose the Heartbleed vulnerability.
+
+**Compliant Solution (Heartbleed)**
+
+OpenSSL version 1.0.1g contains the following patch, which guarantees that `payload` is within a valid range. The range is limited by the size of the input record.
+
+```cpp
+int dtls1_process_heartbeat(SSL *s) {
+ unsigned char *p = &s->s3->rrec.data[0], *pl;
+ unsigned short hbtype;
+ unsigned int payload;
+ unsigned int padding = 16; /* Use minimum padding */
+
+ /* ... More code ... */
+
+ /* Read type and payload length first */
+ if (1 + 2 + 16 > s->s3->rrec.length)
+ return 0; /* Silently discard */
+ hbtype = *p++;
+ n2s(p, payload);
+ if (1 + 2 + payload + 16 > s->s3->rrec.length)
+ return 0; /* Silently discard per RFC 6520 */
+ pl = p;
+
+ /* ... More code ... */
+
+ if (hbtype == TLS1_HB_REQUEST) {
+ unsigned char *buffer, *bp;
+ int r;
+
+ /*
+ * Allocate memory for the response; size is 1 byte
+ * message type, plus 2 bytes payload length, plus
+ * payload, plus padding.
+ */
+ buffer = OPENSSL_malloc(1 + 2 + payload + padding);
+ bp = buffer;
+ /* Enter response type, length, and copy payload */
+ *bp++ = TLS1_HB_RESPONSE;
+ s2n(payload, bp);
+ memcpy(bp, pl, payload);
+ /* ... More code ... */
+ }
+ /* ... More code ... */
+}
+```
+
+## Risk Assessment
+
+Depending on the library function called, an attacker may be able to use a heap or stack overflow [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) to run arbitrary code.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ARR38-C | High | Likely | Medium | P18 | L1 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | array_out_of_bounds | Supported Astrée reports all out-of-bound accesses within library analysis stubs. The user may provide additional stubs for arbitrary (library) functions. |
CodeSonar | 7.2p0 | LANG.MEM.BO LANG.MEM.BU BADFUNC.BO.\* | Buffer overrun Buffer underrun A collection of warning classes that report uses of library functions prone to internal buffer overflows |
Compass/ROSE | | | |
Coverity | 2017.07 | BUFFER_SIZE BAD_SIZEOF BAD_ALLOC_STRLEN BAD_ALLOC_ARITHMETIC | Implemented |
Fortify SCA | 5.0 | | Can detect violations of this rule with CERT C Rule Pack |
Helix QAC | 2022.4 | C2840 DF2840, DF2841, DF2842, DF2843, DF2845, DF2846, DF2847, DF2848, DF2935, DF2936, DF2937, DF2938, DF4880, DF4881, DF4882, DF4883 | |
Klocwork | 2022.4 | ABV.GENERALABV.GENERAL.MULTIDIMENSION | |
LDRA tool suite | 9.7.1 | 64 X, 66 X, 68 X, 69 X, 70 X, 71 X, 79 X | Partially Implmented |
Parasoft C/C++test | 2022.2 | CERT_C-ARR38-a CERT_C-ARR38-b CERT_C-ARR38-c CERT_C-ARR38-d | Avoid overflow when reading from a buffer Avoid overflow when writing to a buffer Avoid buffer overflow due to defining incorrect format limits Avoid overflow due to reading a not zero terminated string |
Parasoft Insure++ | | | Runtime analysis |
PC-lint Plus | 1.4 | 419, 420 | Partially supported |
Polyspace Bug Finder | R2023a | CERT C: Rule ARR38-C | Checks for: Mismatch between data length and sizeismatch between data length and size, invalid use of standard library memory routinenvalid use of standard library memory routine, possible misuse of sizeofossible misuse of sizeof, buffer overflow from incorrect string format specifieruffer overflow from incorrect string format specifier, invalid use of standard library string routinenvalid use of standard library string routine, destination buffer overflow in string manipulationestination buffer overflow in string manipulation, destination buffer underflow in string manipulationestination buffer underflow in string manipulation. Rule partially covered. |
PRQA QA-C | 9.7 | 2840, 2841, 2842, 2843, 2845, 2846, 2847, 2848, 2935, 2936, 2937, 2938 | Fully implemented |
PRQA QA-C++ | 4.4 | 2840, 2841, 2842, 2843, 2845, 2846, 2847, 2848, 2935, 2936, 2937, 2938 | Fully implemented |
Splint | 3.1.1 | | |
TrustInSoft Analyzer | 1.38 | out of bounds read | Partially verified. |
+
+
+## Related Vulnerabilities
+
+[CVE-2016-2208](https://bugs.chromium.org/p/project-zero/issues/detail?id=820) results from a violation of this rule. The attacker can supply a value used to determine how much data is copied into a buffer via `memcpy()`, resulting in a buffer overlow of attacker-controlled data.
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ARR38-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-121 and ARR38-C**
+
+Intersection( CWE-121, ARR38-C) =
+
+* Stack buffer overflow from passing invalid arguments to library function
+CWE-121 – ARR38-C =
+* Stack buffer overflows from direct out-of-bounds write
+ARR38-C – CWE-121 =
+* Out-of-bounds read from passing invalid arguments to library function
+* Buffer overflow on heap or data segment from passing invalid arguments to library function
+**CWE-119 and ARR38-C**
+
+See CWE-119 and ARR30-C
+
+**CWE-125 and ARR38-C**
+
+Independent( ARR30-C, ARR38-C, EXP39-C, INT30-C)
+
+STR31-C = Subset( Union( ARR30-C, ARR38-C))
+
+STR32-C = Subset( ARR38-C)
+
+Intersection( ARR38-C, CWE-125) =
+
+* Reading from an out-of-bounds array index or off the end of an array via standard library function
+ARR38-C – CWE-125 =
+* Writing to an out-of-bounds array index or off the end of an array via standard library function
+CWE-125 – ARR38-C =
+* Reading beyond a non-array buffer
+* Reading beyond an array directly (using pointer arithmetic, or \[\] notation)
+**CWE-805 and ARR38-C**
+
+Intersection( CWE-805, ARR38-C) =
+
+* Buffer access with incorrect length via passing invalid arguments to library function
+CWE-805 – ARR38-C =
+* Buffer access with incorrect length directly (such as a loop construct)
+ARR38-C – CWE-805 =
+* Out-of-bounds read or write that does not involve incorrect length (could use incorrect offset instead), that uses library function
+**CWE-123 and ARR38-C**
+
+Independent(ARR30-C, ARR38-C)
+
+STR31-C = Subset( Union( ARR30-C, ARR38-C))
+
+STR32-C = Subset( ARR38-C)
+
+CWE-123 includes any operation that allows an attacker to write an arbitrary value to an arbitrary memory location. This could be accomplished via overwriting a pointer with data that refers to the address to write, then when the program writes to a pointed-to value, supplying a malicious value. Vulnerable pointer values can be corrupted by:
+
+* Stack return address
+* Buffer overflow on the heap (which typically overwrites back/next pointer values)
+* Write to untrusted array index (if it is also invalid)
+* Format string exploit
+* Overwriting a C++ object with virtual functions (because it has a virtual pointer)
+* Others?
+Intersection( CWE-123, ARR38-C) =
+* Buffer overflow via passing invalid arguments to library function
+ARR38-C – CWE-123 =
+* Buffer overflow to “harmless” memory from passing invalid arguments to library function
+* Out-of-bounds read from passing invalid arguments to library function
+CWE-123 – ARR38-C =
+* Arbitrary writes that do not involve standard C library functions
+**CWE-129 and ARR38-C**
+
+ARR38-C - CWE-129 = making library functions create invalid pointers without using untrusted data.
+
+E.g. : `char[3] array;`
+
+`strcpy(array, "123456");`
+
+CWE-129 - ARR38-C = not validating an integer used as an array index or in pointer arithmetic
+
+E.g.: `void foo(int i) {`
+
+` char array[3];`
+
+` array[i];`
+
+`}`
+
+Intersection(ARR38-C, CWE-129) = making library functions create invalid pointers using untrusted data.
+
+`eg: void foo(int i) {`
+
+` char src[3], dest[3];`
+
+` memcpy(dest, src, i);`
+
+`}`
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ARR38-C: Guarantee that library functions do not form invalid pointers](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ARR38-C/LibraryFunctionArgumentOutOfBounds.ql b/c/cert/src/rules/ARR38-C/LibraryFunctionArgumentOutOfBounds.ql
new file mode 100644
index 0000000000..04e1c8a505
--- /dev/null
+++ b/c/cert/src/rules/ARR38-C/LibraryFunctionArgumentOutOfBounds.ql
@@ -0,0 +1,30 @@
+/**
+ * @id c/cert/library-function-argument-out-of-bounds
+ * @name ARR38-C: Guarantee that library functions do not form invalid pointers
+ * @description Passing out-of-bounds pointers or erroneous size arguments to standard library
+ * functions can result in out-of-bounds accesses and other undefined behavior.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/arr38-c
+ * correctness
+ * security
+ * external/cert/severity/high
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p18
+ * external/cert/level/l1
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.OutOfBounds
+
+from
+ OOB::BufferAccessLibraryFunctionCall fc, string message, Expr bufferArg, string bufferArgStr,
+ Expr sizeOrOtherBufferArg, string otherStr
+where
+ not isExcluded(fc, OutOfBoundsPackage::libraryFunctionArgumentOutOfBoundsQuery()) and
+ OOB::problems(fc, message, bufferArg, bufferArgStr, sizeOrOtherBufferArg, otherStr)
+select fc, message, bufferArg, bufferArgStr, sizeOrOtherBufferArg, otherStr
diff --git a/c/cert/src/rules/ARR39-C/DoNotAddOrSubtractAScaledIntegerToAPointer.ql b/c/cert/src/rules/ARR39-C/DoNotAddOrSubtractAScaledIntegerToAPointer.ql
index 5ad9fc7f6e..c3ebd6ede6 100644
--- a/c/cert/src/rules/ARR39-C/DoNotAddOrSubtractAScaledIntegerToAPointer.ql
+++ b/c/cert/src/rules/ARR39-C/DoNotAddOrSubtractAScaledIntegerToAPointer.ql
@@ -8,54 +8,19 @@
* @problem.severity error
* @tags external/cert/id/arr39-c
* correctness
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p6
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
+import codingstandards.cpp.types.Pointers
import semmle.code.cpp.dataflow.TaintTracking
-import DataFlow::PathGraph
-
-/**
- * An expression which performs pointer arithmetic
- */
-abstract class PointerArithmeticExpr extends Expr {
- abstract Expr getPointer();
-
- abstract Expr getOperand();
-}
-
-/**
- * A pointer arithmetic binary operation expression.
- */
-class SimplePointerArithmeticExpr extends PointerArithmeticExpr, PointerArithmeticOperation {
- override Expr getPointer() { result = this.getLeftOperand() }
-
- override Expr getOperand() { result = this.getRightOperand() }
-}
-
-/**
- * A pointer arithmetic assignment expression.
- */
-class AssignPointerArithmeticExpr extends PointerArithmeticExpr, AssignOperation {
- AssignPointerArithmeticExpr() {
- this instanceof AssignPointerAddExpr or
- this instanceof AssignPointerSubExpr
- }
-
- override Expr getPointer() { result = this.getLValue() }
-
- override Expr getOperand() { result = this.getRValue() }
-}
-
-/**
- * A pointer arithmetic array access expression.
- */
-class ArrayPointerArithmeticExpr extends PointerArithmeticExpr, ArrayExpr {
- override Expr getPointer() { result = this.getArrayBase() }
-
- override Expr getOperand() { result = this.getArrayOffset() }
-}
+import ScaledIntegerPointerArithmeticFlow::PathGraph
/**
* An expression which invokes the `offsetof` macro or `__builtin_offsetof` operation.
@@ -109,12 +74,10 @@ class ScaledIntegerExpr extends Expr {
* A data-flow configuration modeling data-flow from a `ScaledIntegerExpr` to a
* `PointerArithmeticExpr` where the pointer does not point to a 1-byte type.
*/
-class ScaledIntegerPointerArithmeticConfig extends DataFlow::Configuration {
- ScaledIntegerPointerArithmeticConfig() { this = "ScaledIntegerPointerArithmeticConfig" }
-
- override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ScaledIntegerExpr }
+module ScaledIntegerPointerArithmeticConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ScaledIntegerExpr }
- override predicate isSink(DataFlow::Node sink) {
+ predicate isSink(DataFlow::Node sink) {
exists(PointerArithmeticExpr pa |
// exclude pointers to 1-byte types as they do not scale
pa.getPointer().getFullyConverted().getType().(DerivedType).getBaseType().getSize() != 1 and
@@ -123,9 +86,13 @@ class ScaledIntegerPointerArithmeticConfig extends DataFlow::Configuration {
}
}
-from ScaledIntegerPointerArithmeticConfig config, DataFlow::PathNode src, DataFlow::PathNode sink
+module ScaledIntegerPointerArithmeticFlow = DataFlow::Global;
+
+from
+ ScaledIntegerPointerArithmeticFlow::PathNode src,
+ ScaledIntegerPointerArithmeticFlow::PathNode sink
where
not isExcluded(sink.getNode().asExpr(),
Pointers2Package::doNotAddOrSubtractAScaledIntegerToAPointerQuery()) and
- config.hasFlowPath(src, sink)
+ ScaledIntegerPointerArithmeticFlow::flowPath(src, sink)
select sink, src, sink, "Scaled integer used in pointer arithmetic."
diff --git a/c/cert/src/rules/CON30-C/CleanUpThreadSpecificStorage.ql b/c/cert/src/rules/CON30-C/CleanUpThreadSpecificStorage.ql
index 55f4afe7d8..1e03c089e8 100644
--- a/c/cert/src/rules/CON30-C/CleanUpThreadSpecificStorage.ql
+++ b/c/cert/src/rules/CON30-C/CleanUpThreadSpecificStorage.ql
@@ -9,19 +9,21 @@
* @tags external/cert/id/con30-c
* correctness
* concurrency
+ * external/cert/severity/medium
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
import codingstandards.cpp.Concurrency
-import semmle.code.cpp.dataflow.TaintTracking
import semmle.code.cpp.dataflow.DataFlow
-class TssCreateToTssDeleteDataFlowConfiguration extends DataFlow::Configuration {
- TssCreateToTssDeleteDataFlowConfiguration() { this = "TssCreateToTssDeleteDataFlowConfiguration" }
-
- override predicate isSource(DataFlow::Node node) {
+module TssCreateToTssDeleteConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node node) {
exists(TSSCreateFunctionCall tsc, Expr e |
// the only requirement of the source is that at some point
// it refers to the key of a create statement
@@ -30,7 +32,7 @@ class TssCreateToTssDeleteDataFlowConfiguration extends DataFlow::Configuration
)
}
- override predicate isSink(DataFlow::Node node) {
+ predicate isSink(DataFlow::Node node) {
exists(TSSDeleteFunctionCall tsd, Expr e |
// the only requirement of a sink is that at some point
// it references the key of a delete call.
@@ -40,15 +42,17 @@ class TssCreateToTssDeleteDataFlowConfiguration extends DataFlow::Configuration
}
}
+module TssCreateToTssDeleteFlow = DataFlow::Global;
+
from TSSCreateFunctionCall tcfc
where
not isExcluded(tcfc, Concurrency4Package::cleanUpThreadSpecificStorageQuery()) and
// all calls to `tss_create` must be bookended by calls to tss_delete
// even if a thread is not created.
- not exists(TssCreateToTssDeleteDataFlowConfiguration config |
- config.hasFlow(DataFlow::definitionByReferenceNodeFromArgument(tcfc.getKey()), _)
+ not (
+ TssCreateToTssDeleteFlow::flow(DataFlow::definitionByReferenceNodeFromArgument(tcfc.getKey()), _)
or
- config.hasFlow(DataFlow::exprNode(tcfc.getKey()), _)
+ TssCreateToTssDeleteFlow::flow(DataFlow::exprNode(tcfc.getKey()), _)
)
or
// if a thread is created, we must check additional items
diff --git a/c/cert/src/rules/CON31-C/DoNotAllowAMutexToGoOutOfScopeWhileLocked.ql b/c/cert/src/rules/CON31-C/DoNotAllowAMutexToGoOutOfScopeWhileLocked.ql
index 4e32b6eb15..345623fe0d 100644
--- a/c/cert/src/rules/CON31-C/DoNotAllowAMutexToGoOutOfScopeWhileLocked.ql
+++ b/c/cert/src/rules/CON31-C/DoNotAllowAMutexToGoOutOfScopeWhileLocked.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/con31-c
* correctness
* concurrency
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
@@ -16,7 +21,8 @@ import cpp
import codingstandards.c.cert
import codingstandards.cpp.rules.donotallowamutextogooutofscopewhilelocked.DoNotAllowAMutexToGoOutOfScopeWhileLocked
-class DoNotAllowAMutexToGoOutOfScopeWhileLockedQuery extends DoNotAllowAMutexToGoOutOfScopeWhileLockedSharedQuery {
+class DoNotAllowAMutexToGoOutOfScopeWhileLockedQuery extends DoNotAllowAMutexToGoOutOfScopeWhileLockedSharedQuery
+{
DoNotAllowAMutexToGoOutOfScopeWhileLockedQuery() {
this = Concurrency3Package::doNotAllowAMutexToGoOutOfScopeWhileLockedQuery()
}
diff --git a/c/cert/src/rules/CON31-C/DoNotDestroyAMutexWhileItIsLocked.ql b/c/cert/src/rules/CON31-C/DoNotDestroyAMutexWhileItIsLocked.ql
index b37dccab3a..40c4e936dd 100644
--- a/c/cert/src/rules/CON31-C/DoNotDestroyAMutexWhileItIsLocked.ql
+++ b/c/cert/src/rules/CON31-C/DoNotDestroyAMutexWhileItIsLocked.ql
@@ -8,6 +8,11 @@
* @tags external/cert/id/con31-c
* correctness
* concurrency
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/CON32-C/PreventDataRacesWithMultipleThreads.ql b/c/cert/src/rules/CON32-C/PreventDataRacesWithMultipleThreads.ql
index d4f3cbbe10..3ea9e1e1fd 100644
--- a/c/cert/src/rules/CON32-C/PreventDataRacesWithMultipleThreads.ql
+++ b/c/cert/src/rules/CON32-C/PreventDataRacesWithMultipleThreads.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/con32-c
* correctness
* concurrency
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/CON33-C/RaceConditionsWhenUsingLibraryFunctions.ql b/c/cert/src/rules/CON33-C/RaceConditionsWhenUsingLibraryFunctions.ql
index ff9f0884d7..c9bcaa6bd2 100644
--- a/c/cert/src/rules/CON33-C/RaceConditionsWhenUsingLibraryFunctions.ql
+++ b/c/cert/src/rules/CON33-C/RaceConditionsWhenUsingLibraryFunctions.ql
@@ -8,6 +8,11 @@
* @tags external/cert/id/con33-c
* correctness
* concurrency
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
@@ -24,5 +29,5 @@ where
"setlocale", "atomic_init", "ATOMIC_VAR_INIT", "tmpnam", "mbrtoc16", "c16rtomb", "mbrtoc32",
"c32rtomb"
]
-select node,
- "Concurrent call to non-reeantrant function $@.", node.(FunctionCall).getTarget(), node.(FunctionCall).getTarget().getName()
+select node, "Concurrent call to non-reeantrant function $@.", node.(FunctionCall).getTarget(),
+ node.(FunctionCall).getTarget().getName()
diff --git a/c/cert/src/rules/CON34-C/AppropriateThreadObjectStorageDurations.ql b/c/cert/src/rules/CON34-C/AppropriateThreadObjectStorageDurations.ql
index 71138f4ff8..4fb034406b 100644
--- a/c/cert/src/rules/CON34-C/AppropriateThreadObjectStorageDurations.ql
+++ b/c/cert/src/rules/CON34-C/AppropriateThreadObjectStorageDurations.ql
@@ -9,35 +9,53 @@
* @tags external/cert/id/con34-c
* correctness
* concurrency
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
+import codingstandards.c.Objects
import codingstandards.cpp.Concurrency
-import semmle.code.cpp.dataflow.TaintTracking
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.commons.Alloc
-from C11ThreadCreateCall tcc, StackVariable sv, Expr arg, Expr acc
+from C11ThreadCreateCall tcc, Expr arg
where
not isExcluded(tcc, Concurrency4Package::appropriateThreadObjectStorageDurationsQuery()) and
tcc.getArgument(2) = arg and
- sv.getAnAccess() = acc and
- // a stack variable that is given as an argument to a thread
- TaintTracking::localTaint(DataFlow::exprNode(acc), DataFlow::exprNode(arg)) and
- // or isn't one of the allowed usage patterns
- not exists(Expr mfc |
- isAllocationExpr(mfc) and
- sv.getAnAssignedValue() = mfc and
- acc.getAPredecessor*() = mfc
- ) and
- not exists(TSSGetFunctionCall tsg, TSSSetFunctionCall tss, DataFlow::Node src |
- sv.getAnAssignedValue() = tsg and
- acc.getAPredecessor*() = tsg and
- // there should be dataflow from somewhere (the same somewhere)
- // into each of the first arguments
- DataFlow::localFlow(src, DataFlow::exprNode(tsg.getArgument(0))) and
- DataFlow::localFlow(src, DataFlow::exprNode(tss.getArgument(0)))
+ (
+ exists(ObjectIdentity obj, Expr acc |
+ obj.getASubobjectAccess() = acc and
+ obj.getStorageDuration().isAutomatic() and
+ exists(DataFlow::Node addrNode |
+ (
+ addrNode = DataFlow::exprNode(any(AddressOfExpr e | e.getOperand() = acc))
+ or
+ addrNode = DataFlow::exprNode(acc) and
+ exists(ArrayToPointerConversion c | c.getExpr() = acc)
+ ) and
+ TaintTracking::localTaint(addrNode, DataFlow::exprNode(arg))
+ )
+ )
+ or
+ // TODO: This case is handling threadlocals in a useful way that's not intended to be covered
+ // by the rule. See issue #801. The actual rule should expect no tss_t objects is used, and
+ // this check that this is initialized doesn't seem to belong here. However, it is a useful
+ // check in and of itself, so we should figure out if this is part of an optional rule we
+ // haven't yet implemented and move this behavior there.
+ exists(TSSGetFunctionCall tsg |
+ TaintTracking::localTaint(DataFlow::exprNode(tsg), DataFlow::exprNode(arg)) and
+ not exists(TSSSetFunctionCall tss, DataFlow::Node src |
+ // there should be dataflow from somewhere (the same somewhere)
+ // into each of the first arguments
+ DataFlow::localFlow(src, DataFlow::exprNode(tsg.getArgument(0))) and
+ DataFlow::localFlow(src, DataFlow::exprNode(tss.getArgument(0)))
+ )
+ )
)
select tcc, "$@ not declared with appropriate storage duration", arg, "Shared object"
diff --git a/c/cert/src/rules/CON34-C/ThreadObjectStorageDurationsNotInitialized.ql b/c/cert/src/rules/CON34-C/ThreadObjectStorageDurationsNotInitialized.ql
index ddcddb8dc5..07b114d6ca 100644
--- a/c/cert/src/rules/CON34-C/ThreadObjectStorageDurationsNotInitialized.ql
+++ b/c/cert/src/rules/CON34-C/ThreadObjectStorageDurationsNotInitialized.ql
@@ -10,13 +10,17 @@
* external/cert/audit
* correctness
* concurrency
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
import codingstandards.cpp.Concurrency
-import semmle.code.cpp.dataflow.TaintTracking
import semmle.code.cpp.dataflow.DataFlow
from TSSGetFunctionCall tsg, ThreadedFunction tf
diff --git a/c/cert/src/rules/CON35-C/DeadlockByLockingInPredefinedOrder.ql b/c/cert/src/rules/CON35-C/DeadlockByLockingInPredefinedOrder.ql
index 00fa021878..764b0f263f 100644
--- a/c/cert/src/rules/CON35-C/DeadlockByLockingInPredefinedOrder.ql
+++ b/c/cert/src/rules/CON35-C/DeadlockByLockingInPredefinedOrder.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/con35-c
* correctness
* concurrency
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
@@ -16,7 +21,8 @@ import cpp
import codingstandards.c.cert
import codingstandards.cpp.rules.preventdeadlockbylockinginpredefinedorder.PreventDeadlockByLockingInPredefinedOrder
-class DeadlockByLockingInPredefinedOrderQuery extends PreventDeadlockByLockingInPredefinedOrderSharedQuery {
+class DeadlockByLockingInPredefinedOrderQuery extends PreventDeadlockByLockingInPredefinedOrderSharedQuery
+{
DeadlockByLockingInPredefinedOrderQuery() {
this = Concurrency2Package::deadlockByLockingInPredefinedOrderQuery()
}
diff --git a/c/cert/src/rules/CON36-C/WrapFunctionsThatCanSpuriouslyWakeUpInLoop.ql b/c/cert/src/rules/CON36-C/WrapFunctionsThatCanSpuriouslyWakeUpInLoop.ql
index 430a0e7c19..d0d948d9b2 100644
--- a/c/cert/src/rules/CON36-C/WrapFunctionsThatCanSpuriouslyWakeUpInLoop.ql
+++ b/c/cert/src/rules/CON36-C/WrapFunctionsThatCanSpuriouslyWakeUpInLoop.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/con36-c
* correctness
* concurrency
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p2
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/CON37-C/DoNotCallSignalInMultithreadedProgram.ql b/c/cert/src/rules/CON37-C/DoNotCallSignalInMultithreadedProgram.ql
index 68e0c97ea9..17691f24dd 100644
--- a/c/cert/src/rules/CON37-C/DoNotCallSignalInMultithreadedProgram.ql
+++ b/c/cert/src/rules/CON37-C/DoNotCallSignalInMultithreadedProgram.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/con37-c
* correctness
* concurrency
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/low
+ * external/cert/priority/p6
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
@@ -24,5 +29,4 @@ where
not isExcluded(fc, Concurrency1Package::doNotCallSignalInMultithreadedProgramQuery()) and
fc.getTarget().getName() = "signal" and
exists(ThreadedFunction f)
-select fc,
- "Call to `signal()` in multithreaded programs."
+select fc, "Call to `signal()` in multithreaded programs."
diff --git a/c/cert/src/rules/CON38-C/PreserveSafetyWhenUsingConditionVariables.ql b/c/cert/src/rules/CON38-C/PreserveSafetyWhenUsingConditionVariables.ql
index dad45dd592..3b2ae558d8 100644
--- a/c/cert/src/rules/CON38-C/PreserveSafetyWhenUsingConditionVariables.ql
+++ b/c/cert/src/rules/CON38-C/PreserveSafetyWhenUsingConditionVariables.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/con38-c
* correctness
* concurrency
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p2
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
@@ -16,7 +21,8 @@ import cpp
import codingstandards.c.cert
import codingstandards.cpp.rules.preservesafetywhenusingconditionvariables.PreserveSafetyWhenUsingConditionVariables
-class PreserveSafetyWhenUsingConditionVariablesQuery extends PreserveSafetyWhenUsingConditionVariablesSharedQuery {
+class PreserveSafetyWhenUsingConditionVariablesQuery extends PreserveSafetyWhenUsingConditionVariablesSharedQuery
+{
PreserveSafetyWhenUsingConditionVariablesQuery() {
this = Concurrency3Package::preserveSafetyWhenUsingConditionVariablesQuery()
}
diff --git a/c/cert/src/rules/CON39-C/ThreadWasPreviouslyJoinedOrDetached.ql b/c/cert/src/rules/CON39-C/ThreadWasPreviouslyJoinedOrDetached.ql
index a8dead535d..6ef617ca72 100644
--- a/c/cert/src/rules/CON39-C/ThreadWasPreviouslyJoinedOrDetached.ql
+++ b/c/cert/src/rules/CON39-C/ThreadWasPreviouslyJoinedOrDetached.ql
@@ -9,42 +9,20 @@
* @tags external/cert/id/con39-c
* correctness
* concurrency
+ * external/cert/severity/low
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p6
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
-import codingstandards.cpp.Concurrency
+import codingstandards.cpp.rules.joinordetachthreadonlyonce.JoinOrDetachThreadOnlyOnce
-// OK
-// 1) Thread calls detach parent DOES NOT call join
-// 2) Parent calls join, thread does NOT call detach()
-// NOT OK
-// 1) Thread calls detach, parent calls join
-// 2) Thread calls detach twice, parent does not call join
-// 3) Parent calls join twice, thread does not call detach
-from C11ThreadCreateCall tcc
-where
- not isExcluded(tcc, Concurrency5Package::threadWasPreviouslyJoinedOrDetachedQuery()) and
- // Note: These cases can be simplified but they are presented like this for clarity
- // case 1 - calls to `thrd_join` and `thrd_detach` within the parent or
- // within the parent / child CFG.
- exists(C11ThreadWait tw, C11ThreadDetach dt |
- tw = getAThreadContextAwareSuccessor(tcc) and
- dt = getAThreadContextAwareSuccessor(tcc)
- )
- or
- // case 2 - multiple calls to `thrd_detach` within the threaded CFG.
- exists(C11ThreadDetach dt1, C11ThreadDetach dt2 |
- dt1 = getAThreadContextAwareSuccessor(tcc) and
- dt2 = getAThreadContextAwareSuccessor(tcc) and
- not dt1 = dt2
- )
- or
- // case 3 - multiple calls to `thrd_join` within the threaded CFG.
- exists(C11ThreadWait tw1, C11ThreadWait tw2 |
- tw1 = getAThreadContextAwareSuccessor(tcc) and
- tw2 = getAThreadContextAwareSuccessor(tcc) and
- not tw1 = tw2
- )
-select tcc, "Thread may call join or detach after the thread is joined or detached."
+class ThreadWasPreviouslyJoinedOrDetachedQuery extends JoinOrDetachThreadOnlyOnceSharedQuery {
+ ThreadWasPreviouslyJoinedOrDetachedQuery() {
+ this = Concurrency5Package::threadWasPreviouslyJoinedOrDetachedQuery()
+ }
+}
diff --git a/c/cert/src/rules/CON40-C/AtomicVariableTwiceInExpression.ql b/c/cert/src/rules/CON40-C/AtomicVariableTwiceInExpression.ql
index d20663bd87..0ec195868f 100644
--- a/c/cert/src/rules/CON40-C/AtomicVariableTwiceInExpression.ql
+++ b/c/cert/src/rules/CON40-C/AtomicVariableTwiceInExpression.ql
@@ -9,11 +9,17 @@
* @tags external/cert/id/con40-c
* correctness
* concurrency
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
+import codingstandards.cpp.Concurrency
from MacroInvocation mi, Variable v, Locatable whereFound
where
@@ -22,13 +28,13 @@ where
// There isn't a way to safely use this construct in a way that is also
// possible the reliably detect so advise against using it.
(
- mi.getMacroName() = ["atomic_store", "atomic_store_explicit"]
+ mi instanceof AtomicStore
or
// This construct is generally safe, but must be used in a loop. To lower
// the false positive rate we don't look at the conditions of the loop and
// instead assume if it is found in a looping construct that it is likely
// related to the safety property.
- mi.getMacroName() = ["atomic_compare_exchange_weak", "atomic_compare_exchange_weak_explicit"] and
+ mi instanceof AtomicCompareExchange and
not exists(Loop l | mi.getAGeneratedElement().(Expr).getParent*() = l)
) and
whereFound = mi
diff --git a/c/cert/src/rules/CON41-C/WrapFunctionsThatCanFailSpuriouslyInLoop.ql b/c/cert/src/rules/CON41-C/WrapFunctionsThatCanFailSpuriouslyInLoop.ql
index 62c8ec5dc4..57be1bc488 100644
--- a/c/cert/src/rules/CON41-C/WrapFunctionsThatCanFailSpuriouslyInLoop.ql
+++ b/c/cert/src/rules/CON41-C/WrapFunctionsThatCanFailSpuriouslyInLoop.ql
@@ -9,31 +9,24 @@
* @tags external/cert/id/con41-c
* correctness
* concurrency
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p2
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
+import codingstandards.cpp.Concurrency
-/**
- * Models calls to routines in the `stdatomic` library. Note that these
- * are typically implemented as macros within Clang and GCC's standard
- * libraries.
- */
-class SpuriouslyFailingFunctionCallType extends MacroInvocation {
- SpuriouslyFailingFunctionCallType() {
- getMacroName() = ["atomic_compare_exchange_weak", "atomic_compare_exchange_weak_explicit"]
- }
-}
-
-from SpuriouslyFailingFunctionCallType fc
+from AtomicCompareExchange ace
where
- not isExcluded(fc, Concurrency3Package::wrapFunctionsThatCanFailSpuriouslyInLoopQuery()) and
+ not isExcluded(ace, Concurrency3Package::wrapFunctionsThatCanFailSpuriouslyInLoopQuery()) and
(
- exists(StmtParent sp | sp = fc.getStmt() and not sp.(Stmt).getParentStmt*() instanceof Loop)
+ forex(StmtParent sp | sp = ace.getStmt() | not sp.(Stmt).getParentStmt*() instanceof Loop)
or
- exists(StmtParent sp |
- sp = fc.getExpr() and not sp.(Expr).getEnclosingStmt().getParentStmt*() instanceof Loop
- )
+ forex(Expr e | e = ace.getExpr() | not e.getEnclosingStmt().getParentStmt*() instanceof Loop)
)
-select fc, "Function that can spuriously fail not wrapped in a loop."
+select ace, "Function that can spuriously fail not wrapped in a loop."
diff --git a/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsFunctionReturn.md b/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsFunctionReturn.md
new file mode 100644
index 0000000000..8124cd49cd
--- /dev/null
+++ b/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsFunctionReturn.md
@@ -0,0 +1,190 @@
+# DCL30-C: Declare objects with appropriate storage durations
+
+This query implements the CERT-C rule DCL30-C:
+
+> Declare objects with appropriate storage durations
+
+
+## Description
+
+Every object has a storage duration that determines its lifetime: *static*, *thread*, *automatic*, or *allocated*.
+
+According to the C Standard, 6.2.4, paragraph 2 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\],
+
+> The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to reaches the end of its lifetime.
+
+
+Do not attempt to access an object outside of its lifetime. Attempting to do so is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) and can lead to an exploitable [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability). (See also [undefined behavior 9](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_9) in the C Standard, Annex J.)
+
+## Noncompliant Code Example (Differing Storage Durations)
+
+In this noncompliant code example, the address of the variable `c_str` with automatic storage duration is assigned to the variable `p`, which has static storage duration. The assignment itself is valid, but it is invalid for `c_str` to go out of scope while `p` holds its address, as happens at the end of `dont_do_this``()`.
+
+```cpp
+#include
+
+const char *p;
+void dont_do_this(void) {
+ const char c_str[] = "This will change";
+ p = c_str; /* Dangerous */
+}
+
+void innocuous(void) {
+ printf("%s\n", p);
+}
+
+int main(void) {
+ dont_do_this();
+ innocuous();
+ return 0;
+}
+```
+
+## Compliant Solution (Same Storage Durations)
+
+In this compliant solution, `p` is declared with the same storage duration as `c_str`, preventing `p` from taking on an [indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue) outside of `this_is_OK()`:
+
+```cpp
+void this_is_OK(void) {
+ const char c_str[] = "Everything OK";
+ const char *p = c_str;
+ /* ... */
+}
+/* p is inaccessible outside the scope of string c_str */
+
+```
+Alternatively, both `p` and `c_str` could be declared with static storage duration.
+
+## Compliant Solution (Differing Storage Durations)
+
+If it is necessary for `p` to be defined with static storage duration but `c_str` with a more limited duration, then `p` can be set to `NULL` before `c_str` is destroyed. This practice prevents `p` from taking on an [indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue), although any references to `p` must check for `NULL`.
+
+```cpp
+const char *p;
+void is_this_OK(void) {
+ const char c_str[] = "Everything OK?";
+ p = c_str;
+ /* ... */
+ p = NULL;
+}
+
+```
+
+## Noncompliant Code Example (Return Values)
+
+In this noncompliant code sample, the function `init_array``()` returns a pointer to a character array with automatic storage duration, which is accessible to the caller:
+
+```cpp
+char *init_array(void) {
+ char array[10];
+ /* Initialize array */
+ return array;
+}
+
+```
+Some compilers generate a diagnostic message when a pointer to an object with automatic storage duration is returned from a function, as in this example. Programmers should compile code at high warning levels and resolve any diagnostic messages. (See [MSC00-C. Compile cleanly at high warning levels](https://wiki.sei.cmu.edu/confluence/display/c/MSC00-C.+Compile+cleanly+at+high+warning+levels).)
+
+## Compliant Solution (Return Values)
+
+The solution, in this case, depends on the intent of the programmer. If the intent is to modify the value of `array` and have that modification persist outside the scope of `init_array()`, the desired behavior can be achieved by declaring `array` elsewhere and passing it as an argument to `init_array()`:
+
+```cpp
+#include
+void init_array(char *array, size_t len) {
+ /* Initialize array */
+ return;
+}
+
+int main(void) {
+ char array[10];
+ init_array(array, sizeof(array) / sizeof(array[0]));
+ /* ... */
+ return 0;
+}
+
+```
+
+## Noncompliant Code Example (Output Parameter)
+
+In this noncompliant code example, the function `squirrel_away()` stores a pointer to local variable `local` into a location pointed to by function parameter `ptr_param`. Upon the return of `squirrel_away()`, the pointer `ptr_param` points to a variable that has an expired lifetime.
+
+```cpp
+void squirrel_away(char **ptr_param) {
+ char local[10];
+ /* Initialize array */
+ *ptr_param = local;
+}
+
+void rodent(void) {
+ char *ptr;
+ squirrel_away(&ptr);
+ /* ptr is live but invalid here */
+}
+
+```
+
+## Compliant Solution (Output Parameter)
+
+In this compliant solution, the variable `local` has static storage duration; consequently, `ptr` can be used to reference the `local` array within the `rodent()` function:
+
+```cpp
+char local[10];
+
+void squirrel_away(char **ptr_param) {
+ /* Initialize array */
+ *ptr_param = local;
+}
+
+void rodent(void) {
+ char *ptr;
+ squirrel_away(&ptr);
+ /* ptr is valid in this scope */
+}
+
+```
+
+## Risk Assessment
+
+Referencing an object outside of its lifetime can result in an attacker being able to execute arbitrary code.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
DCL30-C | High | Probable | High | P6 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | pointered-deallocation return-reference-local | Fully checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-DCL30 | Fully implemented |
CodeSonar | 7.2p0 | LANG.STRUCT.RPL | Returns pointer to local |
Compass/ROSE | | | Can detect violations of this rule. It automatically detects returning pointers to local variables. Detecting more general cases, such as examples where static pointers are set to local variables which then go out of scope, would be difficult |
Coverity | 2017.07 | RETURN_LOCAL | Finds many instances where a function will return a pointer to a local stack variable. Coverity Prevent cannot discover all violations of this rule, so further verification is necessary |
Helix QAC | 2022.4 | C3217, C3225, C3230, C4140 C++2515, C++2516, C++2527, C++2528, C++4026, C++4624, C++4629 | |
Klocwork | 2022.4 | LOCRET.ARGLOCRET.GLOB LOCRET.RET | |
LDRA tool suite | 9.7.1 | 42 D, 77 D, 71 S, 565 S | Enhanced Enforcement |
Parasoft C/C++test | 2022.2 | CERT_C-DCL30-a CERT_C-DCL30-b | The address of an object with automatic storage shall not be returned from a function The address of an object with automatic storage shall not be assigned to another object that may persist after the first object has ceased to exist |
PC-lint Plus | 1.4 | 604, 674, 733, 789 | Partially supported |
Polyspace Bug Finder | R2022b | CERT C: Rule DCL30-C | Checks for pointer or reference to stack variable leaving scope (rule fully covered) |
PRQA QA-C | 9.7 | 3217, 3225, 3230, 4140 | Partially implemented |
PRQA QA-C++ | 4.4 | 2515, 2516, 2527, 2528, 4026, 4624, 4629 | |
PVS-Studio | 7.23 | V506 , V507 , V558 , V623 , V723 , V738 | |
RuleChecker | 22.04 | return-reference-local | Partially checked |
Splint | 3.1.1 | | |
TrustInSoft Analyzer | 1.38 | dangling_pointer | Exhaustively detects undefined behavior (see one compliant and one non-compliant example ). |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+DCL30-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-562 and DCL30-C**
+
+DCL30-C = Union( CWE-562, list) where list =
+
+* Assigning a stack pointer to an argument (thereby letting it outlive the current function
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+The rule checks specifically for pointers to objects with automatic storage duration that are returned by functions or assigned to function output parameters.
+
+## References
+
+* CERT-C: [DCL30-C: Declare objects with appropriate storage durations](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsFunctionReturn.ql b/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsFunctionReturn.ql
new file mode 100644
index 0000000000..2e1064ee9d
--- /dev/null
+++ b/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsFunctionReturn.ql
@@ -0,0 +1,58 @@
+/**
+ * @id c/cert/appropriate-storage-durations-function-return
+ * @name DCL30-C: Declare objects with appropriate storage durations
+ * @description When pointers to local variables are returned by a function it can lead to referring
+ * to objects outside of their lifetime, which is undefined behaviour.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/dcl30-c
+ * correctness
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p6
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.Objects
+import semmle.code.cpp.dataflow.DataFlow
+
+class Source extends Expr {
+ ObjectIdentity rootObject;
+
+ Source() {
+ rootObject.getStorageDuration().isAutomatic() and
+ this = rootObject.getASubobjectAddressExpr()
+ }
+}
+
+class Sink extends DataFlow::Node {
+ Sink() {
+ //output parameter
+ exists(Parameter f |
+ f.getAnAccess() = this.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() and
+ f.getUnderlyingType() instanceof PointerType
+ )
+ or
+ //function returns pointer
+ exists(Function f, ReturnStmt r |
+ f.getType() instanceof PointerType and
+ r.getEnclosingFunction() = f and
+ r.getExpr() = this.asExpr()
+ )
+ }
+}
+
+from DataFlow::Node src, DataFlow::Node sink
+where
+ not isExcluded(sink.asExpr(),
+ Declarations8Package::appropriateStorageDurationsFunctionReturnQuery()) and
+ exists(Source s | src.asExpr() = s) and
+ sink instanceof Sink and
+ DataFlow::localFlow(src, sink)
+select sink, "$@ with automatic storage may be accessible outside of its lifetime.", src,
+ src.toString()
diff --git a/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsStackAdressEscape.md b/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsStackAdressEscape.md
new file mode 100644
index 0000000000..1926ffd7aa
--- /dev/null
+++ b/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsStackAdressEscape.md
@@ -0,0 +1,190 @@
+# DCL30-C: Declare objects with appropriate storage durations
+
+This query implements the CERT-C rule DCL30-C:
+
+> Declare objects with appropriate storage durations
+
+
+## Description
+
+Every object has a storage duration that determines its lifetime: *static*, *thread*, *automatic*, or *allocated*.
+
+According to the C Standard, 6.2.4, paragraph 2 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\],
+
+> The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to reaches the end of its lifetime.
+
+
+Do not attempt to access an object outside of its lifetime. Attempting to do so is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) and can lead to an exploitable [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability). (See also [undefined behavior 9](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_9) in the C Standard, Annex J.)
+
+## Noncompliant Code Example (Differing Storage Durations)
+
+In this noncompliant code example, the address of the variable `c_str` with automatic storage duration is assigned to the variable `p`, which has static storage duration. The assignment itself is valid, but it is invalid for `c_str` to go out of scope while `p` holds its address, as happens at the end of `dont_do_this``()`.
+
+```cpp
+#include
+
+const char *p;
+void dont_do_this(void) {
+ const char c_str[] = "This will change";
+ p = c_str; /* Dangerous */
+}
+
+void innocuous(void) {
+ printf("%s\n", p);
+}
+
+int main(void) {
+ dont_do_this();
+ innocuous();
+ return 0;
+}
+```
+
+## Compliant Solution (Same Storage Durations)
+
+In this compliant solution, `p` is declared with the same storage duration as `c_str`, preventing `p` from taking on an [indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue) outside of `this_is_OK()`:
+
+```cpp
+void this_is_OK(void) {
+ const char c_str[] = "Everything OK";
+ const char *p = c_str;
+ /* ... */
+}
+/* p is inaccessible outside the scope of string c_str */
+
+```
+Alternatively, both `p` and `c_str` could be declared with static storage duration.
+
+## Compliant Solution (Differing Storage Durations)
+
+If it is necessary for `p` to be defined with static storage duration but `c_str` with a more limited duration, then `p` can be set to `NULL` before `c_str` is destroyed. This practice prevents `p` from taking on an [indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue), although any references to `p` must check for `NULL`.
+
+```cpp
+const char *p;
+void is_this_OK(void) {
+ const char c_str[] = "Everything OK?";
+ p = c_str;
+ /* ... */
+ p = NULL;
+}
+
+```
+
+## Noncompliant Code Example (Return Values)
+
+In this noncompliant code sample, the function `init_array``()` returns a pointer to a character array with automatic storage duration, which is accessible to the caller:
+
+```cpp
+char *init_array(void) {
+ char array[10];
+ /* Initialize array */
+ return array;
+}
+
+```
+Some compilers generate a diagnostic message when a pointer to an object with automatic storage duration is returned from a function, as in this example. Programmers should compile code at high warning levels and resolve any diagnostic messages. (See [MSC00-C. Compile cleanly at high warning levels](https://wiki.sei.cmu.edu/confluence/display/c/MSC00-C.+Compile+cleanly+at+high+warning+levels).)
+
+## Compliant Solution (Return Values)
+
+The solution, in this case, depends on the intent of the programmer. If the intent is to modify the value of `array` and have that modification persist outside the scope of `init_array()`, the desired behavior can be achieved by declaring `array` elsewhere and passing it as an argument to `init_array()`:
+
+```cpp
+#include
+void init_array(char *array, size_t len) {
+ /* Initialize array */
+ return;
+}
+
+int main(void) {
+ char array[10];
+ init_array(array, sizeof(array) / sizeof(array[0]));
+ /* ... */
+ return 0;
+}
+
+```
+
+## Noncompliant Code Example (Output Parameter)
+
+In this noncompliant code example, the function `squirrel_away()` stores a pointer to local variable `local` into a location pointed to by function parameter `ptr_param`. Upon the return of `squirrel_away()`, the pointer `ptr_param` points to a variable that has an expired lifetime.
+
+```cpp
+void squirrel_away(char **ptr_param) {
+ char local[10];
+ /* Initialize array */
+ *ptr_param = local;
+}
+
+void rodent(void) {
+ char *ptr;
+ squirrel_away(&ptr);
+ /* ptr is live but invalid here */
+}
+
+```
+
+## Compliant Solution (Output Parameter)
+
+In this compliant solution, the variable `local` has static storage duration; consequently, `ptr` can be used to reference the `local` array within the `rodent()` function:
+
+```cpp
+char local[10];
+
+void squirrel_away(char **ptr_param) {
+ /* Initialize array */
+ *ptr_param = local;
+}
+
+void rodent(void) {
+ char *ptr;
+ squirrel_away(&ptr);
+ /* ptr is valid in this scope */
+}
+
+```
+
+## Risk Assessment
+
+Referencing an object outside of its lifetime can result in an attacker being able to execute arbitrary code.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
DCL30-C | High | Probable | High | P6 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | pointered-deallocation return-reference-local | Fully checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-DCL30 | Fully implemented |
CodeSonar | 7.2p0 | LANG.STRUCT.RPL | Returns pointer to local |
Compass/ROSE | | | Can detect violations of this rule. It automatically detects returning pointers to local variables. Detecting more general cases, such as examples where static pointers are set to local variables which then go out of scope, would be difficult |
Coverity | 2017.07 | RETURN_LOCAL | Finds many instances where a function will return a pointer to a local stack variable. Coverity Prevent cannot discover all violations of this rule, so further verification is necessary |
Helix QAC | 2022.4 | C3217, C3225, C3230, C4140 C++2515, C++2516, C++2527, C++2528, C++4026, C++4624, C++4629 | |
Klocwork | 2022.4 | LOCRET.ARGLOCRET.GLOB LOCRET.RET | |
LDRA tool suite | 9.7.1 | 42 D, 77 D, 71 S, 565 S | Enhanced Enforcement |
Parasoft C/C++test | 2022.2 | CERT_C-DCL30-a CERT_C-DCL30-b | The address of an object with automatic storage shall not be returned from a function The address of an object with automatic storage shall not be assigned to another object that may persist after the first object has ceased to exist |
PC-lint Plus | 1.4 | 604, 674, 733, 789 | Partially supported |
Polyspace Bug Finder | R2022b | CERT C: Rule DCL30-C | Checks for pointer or reference to stack variable leaving scope (rule fully covered) |
PRQA QA-C | 9.7 | 3217, 3225, 3230, 4140 | Partially implemented |
PRQA QA-C++ | 4.4 | 2515, 2516, 2527, 2528, 4026, 4624, 4629 | |
PVS-Studio | 7.23 | V506 , V507 , V558 , V623 , V723 , V738 | |
RuleChecker | 22.04 | return-reference-local | Partially checked |
Splint | 3.1.1 | | |
TrustInSoft Analyzer | 1.38 | dangling_pointer | Exhaustively detects undefined behavior (see one compliant and one non-compliant example ). |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+DCL30-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-562 and DCL30-C**
+
+DCL30-C = Union( CWE-562, list) where list =
+
+* Assigning a stack pointer to an argument (thereby letting it outlive the current function
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+The rule checks specifically for pointers to objects with automatic storage duration that are assigned to static storage duration variables.
+
+## References
+
+* CERT-C: [DCL30-C: Declare objects with appropriate storage durations](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsStackAdressEscape.ql b/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsStackAdressEscape.ql
new file mode 100644
index 0000000000..a5749aa8bc
--- /dev/null
+++ b/c/cert/src/rules/DCL30-C/AppropriateStorageDurationsStackAdressEscape.ql
@@ -0,0 +1,28 @@
+/**
+ * @id c/cert/appropriate-storage-durations-stack-adress-escape
+ * @name DCL30-C: Declare objects with appropriate storage durations
+ * @description When storage durations are not compatible between assigned pointers it can lead to
+ * referring to objects outside of their lifetime, which is undefined behaviour.
+ * @kind problem
+ * @precision very-high
+ * @problem.severity error
+ * @tags external/cert/id/dcl30-c
+ * correctness
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p6
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.rules.donotcopyaddressofautostorageobjecttootherobject.DoNotCopyAddressOfAutoStorageObjectToOtherObject
+
+class AppropriateStorageDurationsStackAdressEscapeQuery extends DoNotCopyAddressOfAutoStorageObjectToOtherObjectSharedQuery
+{
+ AppropriateStorageDurationsStackAdressEscapeQuery() {
+ this = Declarations8Package::appropriateStorageDurationsStackAdressEscapeQuery()
+ }
+}
diff --git a/c/cert/src/rules/DCL31-C/DeclareIdentifiersBeforeUsingThem.ql b/c/cert/src/rules/DCL31-C/DeclareIdentifiersBeforeUsingThem.ql
index 369baa4a63..35e6cd057a 100644
--- a/c/cert/src/rules/DCL31-C/DeclareIdentifiersBeforeUsingThem.ql
+++ b/c/cert/src/rules/DCL31-C/DeclareIdentifiersBeforeUsingThem.ql
@@ -8,6 +8,11 @@
* @tags external/cert/id/dcl31-c
* correctness
* readability
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/low
+ * external/cert/priority/p3
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/DCL37-C/DoNotDeclareOrDefineAReservedIdentifier.ql b/c/cert/src/rules/DCL37-C/DoNotDeclareOrDefineAReservedIdentifier.ql
index 99c5a9708b..04a3030cc1 100644
--- a/c/cert/src/rules/DCL37-C/DoNotDeclareOrDefineAReservedIdentifier.ql
+++ b/c/cert/src/rules/DCL37-C/DoNotDeclareOrDefineAReservedIdentifier.ql
@@ -9,6 +9,11 @@
* correctness
* maintainability
* readability
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/low
+ * external/cert/priority/p3
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/DCL38-C/DeclaringAFlexibleArrayMember.ql b/c/cert/src/rules/DCL38-C/DeclaringAFlexibleArrayMember.ql
index b5f7087ab0..d6000852c6 100644
--- a/c/cert/src/rules/DCL38-C/DeclaringAFlexibleArrayMember.ql
+++ b/c/cert/src/rules/DCL38-C/DeclaringAFlexibleArrayMember.ql
@@ -10,34 +10,22 @@
* correctness
* maintainability
* readability
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/low
+ * external/cert/priority/p3
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
-
-/**
- * A member with the type array that is last in a struct
- * includes any sized array (either specified or not)
- */
-class FlexibleArrayMember extends MemberVariable {
- Struct s;
-
- FlexibleArrayMember() {
- this.getType() instanceof ArrayType and
- this.getDeclaringType() = s and
- not exists(int i, int j |
- s.getAMember(i) = this and
- exists(s.getAMember(j)) and
- j > i
- )
- }
-}
+import codingstandards.c.Variable
from VariableDeclarationEntry m, ArrayType a
where
not isExcluded(m, Declarations2Package::declaringAFlexibleArrayMemberQuery()) and
m.getType() = a and
- m.getVariable() instanceof FlexibleArrayMember and
+ m.getVariable() instanceof FlexibleArrayMemberCandidate and
a.getArraySize() = 1
select m, "Incorrect syntax used for declaring this flexible array member."
diff --git a/c/cert/src/rules/DCL39-C/InformationLeakageAcrossTrustBoundariesC.md b/c/cert/src/rules/DCL39-C/InformationLeakageAcrossTrustBoundariesC.md
new file mode 100644
index 0000000000..4dd3bcbe3c
--- /dev/null
+++ b/c/cert/src/rules/DCL39-C/InformationLeakageAcrossTrustBoundariesC.md
@@ -0,0 +1,292 @@
+# DCL39-C: Avoid information leakage when passing a structure across a trust boundary
+
+This query implements the CERT-C rule DCL39-C:
+
+> Avoid information leakage when passing a structure across a trust boundary
+
+
+## Description
+
+The C Standard, 6.7.2.1, discusses the layout of structure fields. It specifies that non-bit-field members are aligned in an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) manner and that there may be padding within or at the end of a structure. Furthermore, initializing the members of the structure does not guarantee initialization of the padding bytes. The C Standard, 6.2.6.1, paragraph 6 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], states
+
+> When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values.
+
+
+Additionally, the storage units in which a bit-field resides may also have padding bits. For an object with automatic storage duration, these padding bits do not take on specific values and can contribute to leaking sensitive information.
+
+When passing a pointer to a structure across a trust boundary to a different trusted domain, the programmer must ensure that the padding bytes and bit-field storage unit padding bits of such a structure do not contain sensitive information.
+
+## Noncompliant Code Example
+
+This noncompliant code example runs in kernel space and copies data from `arg` to user space. However, padding bytes may be used within the structure, for example, to ensure the proper alignment of the structure members. These padding bytes may contain sensitive information, which may then be leaked when the data is copied to user space.
+
+```cpp
+#include
+
+struct test {
+ int a;
+ char b;
+ int c;
+};
+
+/* Safely copy bytes to user space */
+extern int copy_to_user(void *dest, void *src, size_t size);
+
+void do_stuff(void *usr_buf) {
+ struct test arg = {.a = 1, .b = 2, .c = 3};
+ copy_to_user(usr_buf, &arg, sizeof(arg));
+}
+
+```
+
+## Noncompliant Code Example (memset())
+
+The padding bytes can be explicitly initialized by calling `memset()`:
+
+```cpp
+#include
+
+struct test {
+ int a;
+ char b;
+ int c;
+};
+
+/* Safely copy bytes to user space */
+extern int copy_to_user(void *dest, void *src, size_t size);
+
+void do_stuff(void *usr_buf) {
+ struct test arg;
+
+ /* Set all bytes (including padding bytes) to zero */
+ memset(&arg, 0, sizeof(arg));
+
+ arg.a = 1;
+ arg.b = 2;
+ arg.c = 3;
+
+ copy_to_user(usr_buf, &arg, sizeof(arg));
+}
+
+```
+However, a conforming compiler is free to implement `arg.b = 2` by setting the low-order bits of a register to 2, leaving the high-order bits unchanged and containing sensitive information. Then the platform copies all register bits into memory, leaving sensitive information in the padding bits. Consequently, this implementation could leak the high-order bits from the register to a user.
+
+## Compliant Solution
+
+This compliant solution serializes the structure data before copying it to an untrusted context:
+
+```cpp
+#include
+#include
+
+struct test {
+ int a;
+ char b;
+ int c;
+};
+
+/* Safely copy bytes to user space */
+extern int copy_to_user(void *dest, void *src, size_t size);
+
+void do_stuff(void *usr_buf) {
+ struct test arg = {.a = 1, .b = 2, .c = 3};
+ /* May be larger than strictly needed */
+ unsigned char buf[sizeof(arg)];
+ size_t offset = 0;
+
+ memcpy(buf + offset, &arg.a, sizeof(arg.a));
+ offset += sizeof(arg.a);
+ memcpy(buf + offset, &arg.b, sizeof(arg.b));
+ offset += sizeof(arg.b);
+ memcpy(buf + offset, &arg.c, sizeof(arg.c));
+ offset += sizeof(arg.c);
+ /* Set all remaining bytes to zero */
+ memset(buf + offset, 0, sizeof(arg) - offset);
+
+ copy_to_user(usr_buf, buf, offset /* size of info copied */);
+}
+```
+This code ensures that no uninitialized padding bytes are copied to unprivileged users. **Important:** The structure copied to user space is now a packed structure and the `copy_to_user()` function (or other eventual user) would need to unpack it to recreate the original padded structure.
+
+## Compliant Solution (Padding Bytes)
+
+Padding bytes can be explicitly declared as fields within the structure. This solution is not portable, however, because it depends on the [implementation](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) and target memory architecture. The following solution is specific to the x86-32 architecture:
+
+```cpp
+#include
+#include
+
+struct test {
+ int a;
+ char b;
+ char padding_1, padding_2, padding_3;
+ int c;
+};
+
+/* Safely copy bytes to user space */
+extern int copy_to_user(void *dest, void *src, size_t size);
+
+void do_stuff(void *usr_buf) {
+ /* Ensure c is the next byte after the last padding byte */
+ static_assert(offsetof(struct test, c) ==
+ offsetof(struct test, padding_3) + 1,
+ "Structure contains intermediate padding");
+ /* Ensure there is no trailing padding */
+ static_assert(sizeof(struct test) ==
+ offsetof(struct test, c) + sizeof(int),
+ "Structure contains trailing padding");
+ struct test arg = {.a = 1, .b = 2, .c = 3};
+ arg.padding_1 = 0;
+ arg.padding_2 = 0;
+ arg.padding_3 = 0;
+ copy_to_user(usr_buf, &arg, sizeof(arg));
+}
+
+```
+The C Standard `static_assert()` macro accepts a constant expression and an [error message](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-error). The expression is evaluated at compile time and, if false, the compilation is terminated and the error message is output. (See [DCL03-C. Use a static assertion to test the value of a constant expression](https://wiki.sei.cmu.edu/confluence/display/c/DCL03-C.+Use+a+static+assertion+to+test+the+value+of+a+constant+expression) for more details.) The explicit insertion of the padding bytes into the `struct` should ensure that no additional padding bytes are added by the compiler and consequently both static assertions should be true. However, it is necessary to validate these assumptions to ensure that the solution is correct for a particular implementation.
+
+## Compliant Solution (Structure Packing—GCC)
+
+GCC allows specifying declaration attributes using the keyword `__attribute__((__packed__))`. When this attribute is present, the compiler will not add padding bytes for memory alignment unless an explicit alignment specifier for a structure member requires the introduction of padding bytes.
+
+```cpp
+#include
+
+struct test {
+ int a;
+ char b;
+ int c;
+} __attribute__((__packed__));
+
+/* Safely copy bytes to user space */
+extern int copy_to_user(void *dest, void *src, size_t size);
+
+void do_stuff(void *usr_buf) {
+ struct test arg = {.a = 1, .b = 2, .c = 3};
+ copy_to_user(usr_buf, &arg, sizeof(arg));
+}
+
+```
+
+## Compliant Solution (Structure Packing—Microsoft Visual Studio)
+
+Microsoft Visual Studio supports `#pragma pack()` to suppress padding bytes \[[MSDN](http://msdn.microsoft.com/en-us/library/2e70t5y1(v=vs.110).aspx)\]. The compiler adds padding bytes for memory alignment, depending on the current packing mode, but still honors the alignment specified by `__declspec(align())`. In this compliant solution, the packing mode is set to 1 in an attempt to ensure all fields are given adjacent offsets:
+
+```cpp
+#include
+
+#pragma pack(push, 1) /* 1 byte */
+struct test {
+ int a;
+ char b;
+ int c;
+};
+#pragma pack(pop)
+
+/* Safely copy bytes to user space */
+extern int copy_to_user(void *dest, void *src, size_t size);
+
+void do_stuff(void *usr_buf) {
+ struct test arg = {1, 2, 3};
+ copy_to_user(usr_buf, &arg, sizeof(arg));
+}
+
+```
+The `pack` pragma takes effect at the first `struct` declaration after the pragma is seen.
+
+## Noncompliant Code Example
+
+This noncompliant code example also runs in kernel space and copies data from `struct test` to user space. However, padding bits will be used within the structure due to the bit-field member lengths not adding up to the number of bits in an `unsigned` object. Further, there is an unnamed bit-field that causes no further bit-fields to be packed into the same storage unit. These padding bits may contain sensitive information, which may then be leaked when the data is copied to user space. For instance, the uninitialized bits may contain a sensitive kernel space pointer value that can be trivially reconstructed by an attacker in user space.
+
+```cpp
+#include
+
+struct test {
+ unsigned a : 1;
+ unsigned : 0;
+ unsigned b : 4;
+};
+
+/* Safely copy bytes to user space */
+extern int copy_to_user(void *dest, void *src, size_t size);
+
+void do_stuff(void *usr_buf) {
+ struct test arg = { .a = 1, .b = 10 };
+ copy_to_user(usr_buf, &arg, sizeof(arg));
+}
+```
+
+## Compliant Solution
+
+Padding bits can be explicitly declared, allowing the programmer to specify the value of those bits. When explicitly declaring all of the padding bits, any unnamed bit-fields of length `0` must be removed from the structure because the explicit padding bits ensure that no further bit-fields will be packed into the same storage unit.
+
+```cpp
+#include
+#include
+#include
+
+struct test {
+ unsigned a : 1;
+ unsigned padding1 : sizeof(unsigned) * CHAR_BIT - 1;
+ unsigned b : 4;
+ unsigned padding2 : sizeof(unsigned) * CHAR_BIT - 4;
+};
+/* Ensure that we have added the correct number of padding bits. */
+static_assert(sizeof(struct test) == sizeof(unsigned) * 2,
+ "Incorrect number of padding bits for type: unsigned");
+
+/* Safely copy bytes to user space */
+extern int copy_to_user(void *dest, void *src, size_t size);
+
+void do_stuff(void *usr_buf) {
+ struct test arg = { .a = 1, .padding1 = 0, .b = 10, .padding2 = 0 };
+ copy_to_user(usr_buf, &arg, sizeof(arg));
+}
+```
+This solution is not portable, however, because it depends on the [implementation](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) and target memory architecture. The explicit insertion of padding bits into the `struct` should ensure that no additional padding bits are added by the compiler. However, it is still necessary to validate these assumptions to ensure that the solution is correct for a particular implementation. For instance, the DEC Alpha is an example of a 64-bit architecture with 32-bit integers that allocates 64 bits to a storage unit.
+
+In addition, this solution assumes that there are no integer padding bits in an `unsigned int`. The portable version of the width calculation from [INT35-C. Use correct integer precisions](https://wiki.sei.cmu.edu/confluence/display/c/INT35-C.+Use+correct+integer+precisions) cannot be used because the bit-field width must be an integer constant expression.
+
+From this situation, it can be seen that special care must be taken because no solution to the bit-field padding issue will be 100% portable.
+
+## Risk Assessment
+
+Padding units might contain sensitive data because the C Standard allows any padding to take [unspecified values](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-unspecifiedvalue). A pointer to such a structure could be passed to other functions, causing information leakage.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
DCL39-C | Low | Unlikely | High | P1 | L3 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | function-argument-with-padding | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-DCL39 | Detects composite structures with padding, in particular those passed to trust boundary routines. |
CodeSonar | 7.2p0 | MISC.PADDING.POTB | Padding Passed Across a Trust Boundary |
Helix QAC | 2022.4 | DF4941, DF4942, DF4943 | |
Klocwork | 2022.4 | PORTING.STORAGE.STRUCT | |
Parasoft C/C++test | 2022.2 | CERT_C-DCL39-a | A pointer to a structure should not be passed to a function that can copy data to the user space |
Polyspace Bug Finder | R2022b | CERT C: Rule DCL39-C | Checks for information leak via structure padding |
PRQA QA-C | 9.7 | 4941, 4942, 4943 | |
PRQA QA-C++ | 4.4 | 4941, 4942, 4943 | |
RuleChecker | 22.04 | function-argument-with-padding | Partially checked |
+
+
+## Related Vulnerabilities
+
+Numerous vulnerabilities in the Linux Kernel have resulted from violations of this rule. [CVE-2010-4083](http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-4083) describes a [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) in which the `semctl()` system call allows unprivileged users to read uninitialized kernel stack memory because various fields of a `semid_ds struct` declared on the stack are not altered or zeroed before being copied back to the user.
+
+[CVE-2010-3881](http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-3881) describes a vulnerability in which structure padding and reserved fields in certain data structures in `QEMU-KVM` were not initialized properly before being copied to user space. A privileged host user with access to `/dev/kvm` could use this [flaw](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-securityflaw) to leak kernel stack memory to user space.
+
+[CVE-2010-3477](http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-3477) describes a kernel information leak in `act_police` where incorrectly initialized structures in the traffic-control dump code may allow the disclosure of kernel memory to user space applications.
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+DCL39-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+The rule does not detect cases where fields may have uninitialized padding but are initialized via an initializer.
+
+## References
+
+* CERT-C: [DCL39-C: Avoid information leakage when passing a structure across a trust boundary](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/DCL39-C/InformationLeakageAcrossTrustBoundariesC.ql b/c/cert/src/rules/DCL39-C/InformationLeakageAcrossTrustBoundariesC.ql
new file mode 100644
index 0000000000..dd2c1217cf
--- /dev/null
+++ b/c/cert/src/rules/DCL39-C/InformationLeakageAcrossTrustBoundariesC.ql
@@ -0,0 +1,28 @@
+/**
+ * @id c/cert/information-leakage-across-trust-boundaries-c
+ * @name DCL39-C: Avoid information leakage when passing a structure across a trust boundary
+ * @description Passing a structure with uninitialized fields or padding bytes can cause information
+ * to be unintentionally leaked.
+ * @kind problem
+ * @precision medium
+ * @problem.severity error
+ * @tags external/cert/id/dcl39-c
+ * security
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p1
+ * external/cert/level/l3
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.rules.informationleakageacrossboundaries.InformationLeakageAcrossBoundaries
+
+class InformationLeakageAcrossTrustBoundariesCQuery extends InformationLeakageAcrossBoundariesSharedQuery
+{
+ InformationLeakageAcrossTrustBoundariesCQuery() {
+ this = Declarations7Package::informationLeakageAcrossTrustBoundariesCQuery()
+ }
+}
diff --git a/c/cert/src/rules/DCL40-C/ExcessLengthNamesIdentifiersNotDistinct.ql b/c/cert/src/rules/DCL40-C/ExcessLengthNamesIdentifiersNotDistinct.ql
index ba2cc5c23f..d002326fae 100644
--- a/c/cert/src/rules/DCL40-C/ExcessLengthNamesIdentifiersNotDistinct.ql
+++ b/c/cert/src/rules/DCL40-C/ExcessLengthNamesIdentifiersNotDistinct.ql
@@ -9,6 +9,11 @@
* correctness
* maintainability
* readability
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p2
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/DCL40-C/IncompatibleFunctionDeclarations.ql b/c/cert/src/rules/DCL40-C/IncompatibleFunctionDeclarations.ql
index 4660e69b68..3811d4e417 100644
--- a/c/cert/src/rules/DCL40-C/IncompatibleFunctionDeclarations.ql
+++ b/c/cert/src/rules/DCL40-C/IncompatibleFunctionDeclarations.ql
@@ -11,35 +11,52 @@
* correctness
* maintainability
* readability
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p2
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
+import codingstandards.cpp.types.Compatible
import ExternalIdentifiers
-//checks if they are incompatible based on return type, number of parameters and parameter types
-predicate checkMatchingFunction(FunctionDeclarationEntry d, FunctionDeclarationEntry d2) {
- not d.getType() = d2.getType()
- or
- not d.getNumberOfParameters() = d2.getNumberOfParameters()
- or
- exists(ParameterDeclarationEntry p, ParameterDeclarationEntry p2, int i |
- d.getParameterDeclarationEntry(i) = p and
- d2.getParameterDeclarationEntry(i) = p2 and
- not p.getType() = p2.getType()
- )
+predicate interestedInFunctions(
+ FunctionDeclarationEntry f1, FunctionDeclarationEntry f2, ExternalIdentifiers d
+) {
+ not f1 = f2 and
+ d = f1.getDeclaration() and
+ d = f2.getDeclaration()
+}
+
+predicate interestedInFunctions(FunctionDeclarationEntry f1, FunctionDeclarationEntry f2) {
+ interestedInFunctions(f1, f2, _)
}
+module FuncDeclEquiv =
+ FunctionDeclarationTypeEquivalence;
+
from ExternalIdentifiers d, FunctionDeclarationEntry f1, FunctionDeclarationEntry f2
where
not isExcluded(f1, Declarations2Package::incompatibleFunctionDeclarationsQuery()) and
not isExcluded(f2, Declarations2Package::incompatibleFunctionDeclarationsQuery()) and
- f1 = d.getADeclarationEntry() and
- f2 = d.getADeclarationEntry() and
- not f1 = f2 and
- f1.getLocation().getStartLine() >= f2.getLocation().getStartLine() and
- f1.getName() = f2.getName() and
- checkMatchingFunction(f1, f2)
+ interestedInFunctions(f1, f2, d) and
+ (
+ //return type check
+ not FuncDeclEquiv::equalReturnTypes(f1, f2)
+ or
+ //parameter type check
+ not FuncDeclEquiv::equalParameterTypes(f1, f2)
+ ) and
+ // Apply ordering on start line, trying to avoid the optimiser applying this join too early
+ // in the pipeline
+ exists(int f1Line, int f2Line |
+ f1.getLocation().hasLocationInfo(_, f1Line, _, _, _) and
+ f2.getLocation().hasLocationInfo(_, f2Line, _, _, _) and
+ f1Line >= f2Line
+ )
select f1, "The object $@ is not compatible with re-declaration $@", f1, f1.getName(), f2,
f2.getName()
diff --git a/c/cert/src/rules/DCL40-C/IncompatibleObjectDeclarations.ql b/c/cert/src/rules/DCL40-C/IncompatibleObjectDeclarations.ql
index 151d33db5c..8e220062d4 100644
--- a/c/cert/src/rules/DCL40-C/IncompatibleObjectDeclarations.ql
+++ b/c/cert/src/rules/DCL40-C/IncompatibleObjectDeclarations.ql
@@ -10,6 +10,11 @@
* correctness
* maintainability
* readability
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p2
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/DCL41-C/VariablesInsideSwitchStatement.ql b/c/cert/src/rules/DCL41-C/VariablesInsideSwitchStatement.ql
index db42f7102c..6f06174b99 100644
--- a/c/cert/src/rules/DCL41-C/VariablesInsideSwitchStatement.ql
+++ b/c/cert/src/rules/DCL41-C/VariablesInsideSwitchStatement.ql
@@ -10,6 +10,11 @@
* correctness
* maintainability
* readability
+ * external/cert/severity/medium
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/ENV30-C/DoNotModifyTheReturnValueOfCertainFunctions.ql b/c/cert/src/rules/ENV30-C/DoNotModifyTheReturnValueOfCertainFunctions.ql
index 42f13f6244..f69a78ba2c 100644
--- a/c/cert/src/rules/ENV30-C/DoNotModifyTheReturnValueOfCertainFunctions.ql
+++ b/c/cert/src/rules/ENV30-C/DoNotModifyTheReturnValueOfCertainFunctions.ql
@@ -8,6 +8,11 @@
* @problem.severity warning
* @tags external/cert/id/env30-c
* correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/ENV31-C/EnvPointerIsInvalidAfterCertainOperations.ql b/c/cert/src/rules/ENV31-C/EnvPointerIsInvalidAfterCertainOperations.ql
index a925b80e74..b4d4a74d57 100644
--- a/c/cert/src/rules/ENV31-C/EnvPointerIsInvalidAfterCertainOperations.ql
+++ b/c/cert/src/rules/ENV31-C/EnvPointerIsInvalidAfterCertainOperations.ql
@@ -8,6 +8,11 @@
* @problem.severity error
* @tags external/cert/id/env31-c
* correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/ENV32-C/ExitHandlersMustReturnNormally.ql b/c/cert/src/rules/ENV32-C/ExitHandlersMustReturnNormally.ql
index 27b6ca2b8e..19cf28b3e9 100644
--- a/c/cert/src/rules/ENV32-C/ExitHandlersMustReturnNormally.ql
+++ b/c/cert/src/rules/ENV32-C/ExitHandlersMustReturnNormally.ql
@@ -8,20 +8,37 @@
* @problem.severity error
* @tags external/cert/id/env32-c
* correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p12
+ * external/cert/level/l1
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
-class ExitFunction extends Function {
- ExitFunction() { this.hasGlobalName(["_Exit", "exit", "quick_exit", "longjmp"]) }
+/**
+ * Exit function or macro.
+ */
+class Exit extends Locatable {
+ Exit() {
+ ["_Exit", "exit", "quick_exit", "longjmp"] = [this.(Function).getName(), this.(Macro).getName()]
+ }
}
-class ExitFunctionCall extends FunctionCall {
- ExitFunctionCall() { this.getTarget() instanceof ExitFunction }
+class ExitExpr extends Expr {
+ ExitExpr() {
+ this.(FunctionCall).getTarget() instanceof Exit
+ or
+ any(MacroInvocation m | this = m.getExpr()).getMacro() instanceof Exit
+ }
}
+/**
+ * Functions that are registered as exit handlers.
+ */
class RegisteredAtexit extends FunctionAccess {
RegisteredAtexit() {
exists(FunctionCall ae |
@@ -32,8 +49,8 @@ class RegisteredAtexit extends FunctionAccess {
}
/**
- * Nodes of type Function, FunctionCall or FunctionAccess that \
- * are reachable from a redistered atexit handler and
+ * Nodes of type Function, FunctionCall, FunctionAccess or ExitExpr
+ * that are reachable from a registered atexit handler and
* can reach an exit function.
*/
class InterestingNode extends ControlFlowNode {
@@ -41,15 +58,17 @@ class InterestingNode extends ControlFlowNode {
exists(Function f |
(
this = f and
- // exit functions are not part of edges
- not this = any(ExitFunction ec)
+ // exit is not part of edges
+ not this instanceof Exit
or
this.(FunctionCall).getTarget() = f
or
this.(FunctionAccess).getTarget() = f
+ or
+ this.(ExitExpr).getEnclosingFunction() = f
) and
- // reaches an exit function
- f.calls*(any(ExitFunction e)) and
+ // reaches an `ExitExpr`
+ f.calls*(any(ExitExpr ee).getEnclosingFunction()) and
// is reachable from a registered atexit function
exists(RegisteredAtexit re | re.getTarget().calls*(f))
)
@@ -62,14 +81,12 @@ class InterestingNode extends ControlFlowNode {
* `Function` and `FunctionCall` in their body.
*/
query predicate edges(InterestingNode a, InterestingNode b) {
- a.(FunctionAccess).getTarget() = b
- or
- a.(FunctionCall).getTarget() = b
- or
+ a.(FunctionAccess).getTarget() = b or
+ a.(FunctionCall).getTarget() = b or
a.(Function).calls(_, b)
}
-from RegisteredAtexit hr, Function f, ExitFunctionCall e
+from RegisteredAtexit hr, Function f, ExitExpr e
where edges(hr, f) and edges+(f, e)
select f, hr, e, "The function is $@ and $@. It must instead terminate by returning.", hr,
"registered as `exit handler`", e, "calls an `exit function`"
diff --git a/c/cert/src/rules/ENV33-C/DoNotCallSystem.ql b/c/cert/src/rules/ENV33-C/DoNotCallSystem.ql
index 58a9c8db79..3b21cd7544 100644
--- a/c/cert/src/rules/ENV33-C/DoNotCallSystem.ql
+++ b/c/cert/src/rules/ENV33-C/DoNotCallSystem.ql
@@ -7,6 +7,11 @@
* @problem.severity error
* @tags external/cert/id/env33-c
* security
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p12
+ * external/cert/level/l1
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/ENV34-C/DoNotStorePointersReturnedByEnvFunctions.ql b/c/cert/src/rules/ENV34-C/DoNotStorePointersReturnedByEnvFunctions.ql
index 505f26046a..af54dfa823 100644
--- a/c/cert/src/rules/ENV34-C/DoNotStorePointersReturnedByEnvFunctions.ql
+++ b/c/cert/src/rules/ENV34-C/DoNotStorePointersReturnedByEnvFunctions.ql
@@ -9,6 +9,11 @@
* @problem.severity error
* @tags external/cert/id/env34-c
* correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/ENV34-C/DoNotStorePointersReturnedByEnvironmentFunWarn.ql b/c/cert/src/rules/ENV34-C/DoNotStorePointersReturnedByEnvironmentFunWarn.ql
index d46b5b36b8..784b7898d6 100644
--- a/c/cert/src/rules/ENV34-C/DoNotStorePointersReturnedByEnvironmentFunWarn.ql
+++ b/c/cert/src/rules/ENV34-C/DoNotStorePointersReturnedByEnvironmentFunWarn.ql
@@ -9,6 +9,11 @@
* @problem.severity warning
* @tags external/cert/id/env34-c
* correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
@@ -16,7 +21,8 @@ import cpp
import codingstandards.c.cert
import codingstandards.cpp.rules.invalidatedenvstringpointerswarn.InvalidatedEnvStringPointersWarn
-class DoNotStorePointersReturnedByEnvironmentFunWarnQuery extends InvalidatedEnvStringPointersWarnSharedQuery {
+class DoNotStorePointersReturnedByEnvironmentFunWarnQuery extends InvalidatedEnvStringPointersWarnSharedQuery
+{
DoNotStorePointersReturnedByEnvironmentFunWarnQuery() {
this = Contracts2Package::doNotStorePointersReturnedByEnvironmentFunWarnQuery()
}
diff --git a/c/cert/src/rules/ERR30-C/ErrnoNotSetToZero.md b/c/cert/src/rules/ERR30-C/ErrnoNotSetToZero.md
new file mode 100644
index 0000000000..417688d1af
--- /dev/null
+++ b/c/cert/src/rules/ERR30-C/ErrnoNotSetToZero.md
@@ -0,0 +1,263 @@
+# ERR30-C: Errno is not set to zero prior to an errno-setting call
+
+This query implements the CERT-C rule ERR30-C:
+
+> Take care when reading errno
+
+
+## Description
+
+The value of `errno` is initialized to zero at program startup, but it is never subsequently set to zero by any C standard library function. The value of `errno` may be set to nonzero by a C standard library function call whether or not there is an error, provided the use of `errno` is not documented in the description of the function. It is meaningful for a program to inspect the contents of `errno` only after an error might have occurred. More precisely, `errno` is meaningful only after a library function that sets `errno` on error has returned an error code.
+
+According to Question 20.4 of C-FAQ \[[Summit 2005](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Summit05)\],
+
+> In general, you should detect errors by checking return values, and use `errno` only to distinguish among the various causes of an error, such as "File not found" or "Permission denied." (Typically, you use `perror` or `strerror` to print these discriminating error messages.) It's only necessary to detect errors with `errno` when a function does not have a unique, unambiguous, out-of-band error return (that is, because all of its possible return values are valid; one example is `atoi [*sic*]`). In these cases (and in these cases only; check the documentation to be sure whether a function allows this), you can detect errors by setting `errno` to 0, calling the function, and then testing `errno`. (Setting `errno` to 0 first is important, as no library function ever does that for you.)
+
+
+Note that `atoi()` is not required to set the value of `errno`.
+
+Library functions fall into the following categories:
+
+* Those that set `errno` and return an [out-of-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-out-of-banderrorindicator)
+* Those that set `errno` and return an [in-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-in-banderrorindicator)
+* Those that do not promise to set `errno`
+* Those with differing standards documentation
+
+## Library Functions that Set errno and Return an Out-of-Band Error Indicator
+
+The C Standard specifies that the functions listed in the following table set `errno` and return an [out-of-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-out-of-banderrorindicator). That is, their return value on error can never be returned by a successful call.
+
+A program may check `errno` after invoking these library functions but is not required to do so. The program should not check the value of `errno` without first verifying that the function returned an error indicator. For example, `errno` should not be checked after calling `signal()` without first ensuring that `signal()` actually returned `SIG_ERR`.
+
+**Functions That Set `errno` and Return an Out-of-Band Error Indicator**
+
+ Function Name | Return Value | errno Value |
ftell() | -1L | Positive |
fgetpos() , fsetpos() | Nonzero | Positive |
mbrtowc() , mbsrtowcs() | (size_t)(-1) | EILSEQ |
signal() | SIG_ERR | Positive |
wcrtomb() , wcsrtombs() | (size_t)(-1) | EILSEQ |
mbrtoc16() , mbrtoc32() | (size_t)(-1) | EILSEQ |
c16rtomb() , c32rtomb() | (size_t)(-1) | EILSEQ |
+
+
+## Library Functions that Set errno and Return an In-Band Error Indicator
+
+The C Standard specifies that the functions listed in the following table set `errno` and return an [in-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-in-banderrorindicator). That is, the return value when an error occurs is also a valid return value for successful calls. For example, the `strtoul()` function returns `ULONG_MAX` and sets `errno` to `ERANGE` if an error occurs. Because `ULONG_MAX` is a valid return value, the only way to confirm that an error occurred when LONG_MAX is returned is to check `errno`.
+
+The `fgetwc()` and `fputwc()` functions return `WEOF` in multiple cases, only one of which results in setting `errno`. The string conversion functions will return the maximum or minimum representable value and set `errno` to `ERANGE` if the converted value cannot be represented by the data type. However, if the conversion cannot happen because the input is invalid, the function will return `0`, and the output pointer parameter will be assigned the value of the input pointer parameter, provided the output parameter is non-null.
+
+A program that uses `errno` for error checking a function that returns an in-band error indicator must set `errno` to `0` before calling one of these library functions and then inspect `errno` before a subsequent library function call.
+
+**Functions that Set `errno` and Return an In-Band Error Indicator**
+
+ Function Name | Return Value | errno Value |
fgetwc() , fputwc() | WEOF | EILSEQ |
strtol() , wcstol() | LONG_MIN or LONG_MAX | ERANGE |
strtoll() , wcstoll() | LLONG_MIN or LLONG_MAX | ERANGE |
strtoul() , wcstoul() | ULONG_MAX | ERANGE |
strtoull() , wcstoull() | ULLONG_MAX | ERANGE |
strtoumax() , wcstoumax() | UINTMAX_MAX | ERANGE |
strtod() , wcstod() | 0 or ±HUGE_VAL | ERANGE |
strtof() , wcstof() | 0 or ±HUGE_VALF | ERANGE |
strtold() , wcstold() | 0 or ±HUGE_VALL | ERANGE |
strtoimax() , wcstoimax() | INTMAX_MIN , INTMAX_MAX | ERANGE |
+
+
+## Library Functions that Do Not Promise to Set errno
+
+The C Standard fails to document the behavior of `errno` for some functions. For example, the `setlocale()` function normally returns a null pointer in the event of an error, but no guarantees are made about setting `errno`.
+
+After calling one of these functions, a program should not rely solely on the value of `errno` to determine if an error occurred. The function might have altered `errno`, but this does not ensure that `errno` will properly indicate an error condition. If the program does check `errno` after calling one of these functions, it should set `errno` to 0 before the function call.
+
+## Library Functions with Differing Standards Documentation
+
+Some functions behave differently regarding `errno` in various standards. The `fopen()` function is one such example. When `fopen()` encounters an error, it returns a null pointer. The C Standard makes no mention of `errno` when describing `fopen()`. However, POSIX.1 declares that when `fopen()` encounters an error, it returns a null pointer and sets `errno` to a value indicating the error \[[IEEE Std 1003.1-2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-IEEEStd1003.1-2013)\]. The implication is that a program conforming to C but not to POSIX (such as a Windows program) should not check `errno` after calling `fopen()`, but a POSIX program may check `errno` if `fopen()` returns a null pointer.
+
+## Library Functions and errno
+
+The following uses of `errno` are documented in the C Standard:
+
+* Functions defined in `` may set `errno` but are not required to.
+* For numeric conversion functions in the `strtod`, `strtol`, `wcstod`, and `wcstol` families, if the correct result is outside the range of representable values, an appropriate minimum or maximum value is returned and the value `ERANGE` is stored in `errno`. For floating-point conversion functions in the `strtod` and `wcstod` families, if an underflow occurs, whether `errno` acquires the value `ERANGE` is [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior). If the conversion fails, `0` is returned and `errno` is not set.
+* The numeric conversion function `atof()` and those in the `atoi` family "need not affect the value of" `errno`.
+* For mathematical functions in ``, if the integer expression `math_errhandling & MATH_ERRNO` is nonzero, on a domain error, `errno` acquires the value `EDOM`; on an overflow with default rounding or if the mathematical result is an exact infinity from finite arguments, `errno` acquires the value `ERANGE`; and on an underflow, whether `errno` acquires the value `ERANGE` is implementation-defined.
+* If a request made by calling `signal()` cannot be honored, a value of `SIG_ERR` is returned and a positive value is stored in `errno`.
+* The byte I/O functions, wide-character I/O functions, and multibyte conversion functions store the value of the macro `EILSEQ` in `errno` if and only if an encoding error occurs.
+* On failure, `fgetpos()` and `fsetpos()` return nonzero and store an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) positive value in `errno`.
+* On failure, `ftell()` returns `-1L` and stores an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) positive value in `errno`.
+* The `perror()` function maps the error number in `errno` to a message and writes it to `stderr`.
+The POSIX.1 standard defines the use of `errno` by many more functions (including the C standard library function). POSIX also has a small set of functions that are exceptions to the rule. These functions have no return value reserved to indicate an error, but they still set `errno` on error. To detect an error, an application must set `errno` to `0` before calling the function and check whether it is nonzero after the call. Affected functions include `strcoll()`, `strxfrm()`, `strerror()`, `wcscoll()`, `wcsxfrm()`, and `fwide()`. The C Standard allows these functions to set `errno` to a nonzero value on success. Consequently, this type of error checking should be performed only on POSIX systems.
+
+## Noncompliant Code Example (strtoul())
+
+This noncompliant code example fails to set `errno` to `0` before invoking `strtoul()`. If an error occurs, `strtoul()` returns a valid value (`ULONG_MAX`), so `errno` is the only means of determining if `strtoul()` ran successfully.
+
+```cpp
+#include
+#include
+#include
+
+void func(const char *c_str) {
+ unsigned long number;
+ char *endptr;
+
+ number = strtoul(c_str, &endptr, 0);
+ if (endptr == c_str || (number == ULONG_MAX
+ && errno == ERANGE)) {
+ /* Handle error */
+ } else {
+ /* Computation succeeded */
+ }
+}
+```
+Any error detected in this manner may have occurred earlier in the program or may not represent an actual error.
+
+## Compliant Solution (strtoul())
+
+This compliant solution sets `errno` to `0` before the call to `strtoul()` and inspects `errno` after the call:
+
+```cpp
+#include
+#include
+#include
+
+void func(const char *c_str) {
+ unsigned long number;
+ char *endptr;
+
+ errno = 0;
+ number = strtoul(c_str, &endptr, 0);
+ if (endptr == c_str || (number == ULONG_MAX
+ && errno == ERANGE)) {
+ /* Handle error */
+ } else {
+ /* Computation succeeded */
+ }
+}
+```
+
+## Noncompliant Code Example (ftell())
+
+This noncompliant code example, after calling `ftell()`, examines `errno` without first checking whether the out-of-band indicator returned by `ftell() `indicates an error.
+
+```cpp
+#include
+#include
+
+void func(FILE* fp) {
+ errno=0;
+ ftell(fp);
+ if (errno) {
+ perror("ftell");
+ }
+}
+```
+
+## Compliant Solution (ftell())
+
+This compliant solution first detects that `ftell() `failed using its out-of-band error indicator. Once an error has been confirmed, reading `errno` (implicitly by using the `perror()` function) is permitted.
+
+```cpp
+#include
+#include
+
+void func(FILE* fp) {
+ if (ftell(fp) == -1) {
+ perror("ftell");
+ }
+}
+```
+
+## Noncompliant Code Example (fopen())
+
+This noncompliant code example may fail to diagnose errors because `fopen()` might not set `errno` even if an error occurs:
+
+```cpp
+#include
+#include
+
+void func(const char *filename) {
+ FILE *fileptr;
+
+ errno = 0;
+ fileptr = fopen(filename, "rb");
+ if (errno != 0) {
+ /* Handle error */
+ }
+}
+```
+
+## Compliant Solution (fopen(), C)
+
+The C Standard makes no mention of `errno` when describing `fopen()`. In this compliant solution, the results of the call to `fopen()` are used to determine failure and `errno` is not checked:
+
+```cpp
+#include
+
+void func(const char *filename) {
+ FILE *fileptr = fopen(filename, "rb");
+ if (fileptr == NULL) {
+ /* An error occurred in fopen() */
+ }
+}
+```
+
+## Compliant Solution (fopen(), POSIX)
+
+In this compliant solution, `errno` is checked only after an error has already been detected by another means:
+
+```cpp
+#include
+#include
+
+void func(const char *filename) {
+ FILE *fileptr;
+
+ errno = 0;
+ fileptr = fopen(filename, "rb");
+ if (fileptr == NULL) {
+ /*
+ * An error occurred in fopen(); now it's valid
+ * to examine errno.
+ */
+ perror(filename);
+ }
+}
+```
+
+## Risk Assessment
+
+The improper use of `errno` may result in failing to detect an error condition or in incorrectly identifying an error condition when none exists.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ERR30-C | Medium | Probable | Medium | P8 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | errno-reset | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-ERR30 | Fully implemented |
CodeSonar | 7.1p0 | LANG.STRUCT.RC | Redundant Condition |
Compass/ROSE | | | Could detect violations of this rule by ensuring that each library function is accompanied by the proper treatment of errno |
Coverity | 2017.07 | MISRA C 2012 Rule 22.8 MISRA C 2012 Rule 22.9 MISRA C 2012 Rule 22.10 | Implemented |
Helix QAC | 2022.3 | C2500, C2501, C2502, C2503 C++3172, C++3173, C++3174, C++3175, C++3176, C++3177, C++3178, C++3179, C++3183, C++3184 | |
Klocwork | 2022.3 | CXX.ERRNO.NOT_SET CXX.ERRNO.NOT_CHECKED CXX.ERRNO.INCORRECTLY_CHECKED | |
LDRA tool suite | 9.7.1 | 111 D, 121 D, 122 D, 132 D, 134 D | Fully implemented |
Parasoft C/C++test | 2022.1 | CERT_C-ERR30-a CERT_C-ERR30-b | Properly use errno value Provide error handling for file opening errors right next to the call to fopen |
Polyspace Bug Finder | R2022b | CERT C: Rule ERR30-C | Checks for: Misuse of errnoisuse of errno, errno not resetrrno not reset. Rule fully covered. |
PRQA QA-C | 9.7 | 2500, 2501, 2502, 2503 | |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ERR30-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-456 and ERR30-C**
+
+CWE-456 = EXP33-C
+
+CWE-456 = Union( ERR30-C, list) where list =
+
+* Reading potentially uninitialized variables besides errno
+**CWE-248 and ERR30-C**
+
+Intersection( CWE-248, ERR30-C) = Ø
+
+CWE-248 is only for languages that support exceptions. It lists C++ and Java, but not C.
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ERR30-C: Take care when reading errno](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ERR30-C/ErrnoNotSetToZero.ql b/c/cert/src/rules/ERR30-C/ErrnoNotSetToZero.ql
new file mode 100644
index 0000000000..06ac9d1198
--- /dev/null
+++ b/c/cert/src/rules/ERR30-C/ErrnoNotSetToZero.ql
@@ -0,0 +1,55 @@
+/**
+ * @id c/cert/errno-not-set-to-zero
+ * @name ERR30-C: Errno is not set to zero prior to an errno-setting call
+ * @description Set errno to zero prior to each call to an errno-setting function. Failing to do so
+ * might end in spurious errno values.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/err30-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.Errno
+
+/**
+ * CFG nodes preceding a `ErrnoSettingFunctionCall`
+ */
+ControlFlowNode notZeroedPriorToErrnoSet(InBandErrnoSettingFunctionCall fc) {
+ result = fc
+ or
+ exists(ControlFlowNode mid |
+ result = mid.getAPredecessor() and
+ mid = notZeroedPriorToErrnoSet(fc) and
+ // stop recursion when `errno` is set to zero
+ not result instanceof ErrnoZeroed and
+ not result = any(ErrnoGuard g).getZeroedSuccessor()
+ )
+}
+
+from InBandErrnoSettingFunctionCall fc, ControlFlowNode cause
+where
+ not isExcluded(cause, Contracts4Package::errnoNotSetToZeroQuery()) and
+ cause = notZeroedPriorToErrnoSet(fc) and
+ (
+ // `errno` is not reset anywhere in the function
+ cause = fc.getEnclosingFunction().getBlock()
+ or
+ // `errno` is not reset after a call to an errno-setting function
+ cause = any(InBandErrnoSettingFunctionCall ec | ec != fc)
+ or
+ // `errno` is not reset after a call to a function
+ cause = any(FunctionCall fc2 | fc2 != fc)
+ or
+ // `errno` value is known to be != 0
+ cause = any(ErrnoGuard g).getNonZeroedSuccessor()
+ )
+select fc, "The value of `errno` may be different than `0` when this function is called."
diff --git a/c/cert/src/rules/ERR30-C/ErrnoReadBeforeReturn.md b/c/cert/src/rules/ERR30-C/ErrnoReadBeforeReturn.md
new file mode 100644
index 0000000000..edfe19bfd4
--- /dev/null
+++ b/c/cert/src/rules/ERR30-C/ErrnoReadBeforeReturn.md
@@ -0,0 +1,263 @@
+# ERR30-C: Do not check errno before the function return value
+
+This query implements the CERT-C rule ERR30-C:
+
+> Take care when reading errno
+
+
+## Description
+
+The value of `errno` is initialized to zero at program startup, but it is never subsequently set to zero by any C standard library function. The value of `errno` may be set to nonzero by a C standard library function call whether or not there is an error, provided the use of `errno` is not documented in the description of the function. It is meaningful for a program to inspect the contents of `errno` only after an error might have occurred. More precisely, `errno` is meaningful only after a library function that sets `errno` on error has returned an error code.
+
+According to Question 20.4 of C-FAQ \[[Summit 2005](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Summit05)\],
+
+> In general, you should detect errors by checking return values, and use `errno` only to distinguish among the various causes of an error, such as "File not found" or "Permission denied." (Typically, you use `perror` or `strerror` to print these discriminating error messages.) It's only necessary to detect errors with `errno` when a function does not have a unique, unambiguous, out-of-band error return (that is, because all of its possible return values are valid; one example is `atoi [*sic*]`). In these cases (and in these cases only; check the documentation to be sure whether a function allows this), you can detect errors by setting `errno` to 0, calling the function, and then testing `errno`. (Setting `errno` to 0 first is important, as no library function ever does that for you.)
+
+
+Note that `atoi()` is not required to set the value of `errno`.
+
+Library functions fall into the following categories:
+
+* Those that set `errno` and return an [out-of-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-out-of-banderrorindicator)
+* Those that set `errno` and return an [in-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-in-banderrorindicator)
+* Those that do not promise to set `errno`
+* Those with differing standards documentation
+
+## Library Functions that Set errno and Return an Out-of-Band Error Indicator
+
+The C Standard specifies that the functions listed in the following table set `errno` and return an [out-of-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-out-of-banderrorindicator). That is, their return value on error can never be returned by a successful call.
+
+A program may check `errno` after invoking these library functions but is not required to do so. The program should not check the value of `errno` without first verifying that the function returned an error indicator. For example, `errno` should not be checked after calling `signal()` without first ensuring that `signal()` actually returned `SIG_ERR`.
+
+**Functions That Set `errno` and Return an Out-of-Band Error Indicator**
+
+ Function Name | Return Value | errno Value |
ftell() | -1L | Positive |
fgetpos() , fsetpos() | Nonzero | Positive |
mbrtowc() , mbsrtowcs() | (size_t)(-1) | EILSEQ |
signal() | SIG_ERR | Positive |
wcrtomb() , wcsrtombs() | (size_t)(-1) | EILSEQ |
mbrtoc16() , mbrtoc32() | (size_t)(-1) | EILSEQ |
c16rtomb() , c32rtomb() | (size_t)(-1) | EILSEQ |
+
+
+## Library Functions that Set errno and Return an In-Band Error Indicator
+
+The C Standard specifies that the functions listed in the following table set `errno` and return an [in-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-in-banderrorindicator). That is, the return value when an error occurs is also a valid return value for successful calls. For example, the `strtoul()` function returns `ULONG_MAX` and sets `errno` to `ERANGE` if an error occurs. Because `ULONG_MAX` is a valid return value, the only way to confirm that an error occurred when LONG_MAX is returned is to check `errno`.
+
+The `fgetwc()` and `fputwc()` functions return `WEOF` in multiple cases, only one of which results in setting `errno`. The string conversion functions will return the maximum or minimum representable value and set `errno` to `ERANGE` if the converted value cannot be represented by the data type. However, if the conversion cannot happen because the input is invalid, the function will return `0`, and the output pointer parameter will be assigned the value of the input pointer parameter, provided the output parameter is non-null.
+
+A program that uses `errno` for error checking a function that returns an in-band error indicator must set `errno` to `0` before calling one of these library functions and then inspect `errno` before a subsequent library function call.
+
+**Functions that Set `errno` and Return an In-Band Error Indicator**
+
+ Function Name | Return Value | errno Value |
fgetwc() , fputwc() | WEOF | EILSEQ |
strtol() , wcstol() | LONG_MIN or LONG_MAX | ERANGE |
strtoll() , wcstoll() | LLONG_MIN or LLONG_MAX | ERANGE |
strtoul() , wcstoul() | ULONG_MAX | ERANGE |
strtoull() , wcstoull() | ULLONG_MAX | ERANGE |
strtoumax() , wcstoumax() | UINTMAX_MAX | ERANGE |
strtod() , wcstod() | 0 or ±HUGE_VAL | ERANGE |
strtof() , wcstof() | 0 or ±HUGE_VALF | ERANGE |
strtold() , wcstold() | 0 or ±HUGE_VALL | ERANGE |
strtoimax() , wcstoimax() | INTMAX_MIN , INTMAX_MAX | ERANGE |
+
+
+## Library Functions that Do Not Promise to Set errno
+
+The C Standard fails to document the behavior of `errno` for some functions. For example, the `setlocale()` function normally returns a null pointer in the event of an error, but no guarantees are made about setting `errno`.
+
+After calling one of these functions, a program should not rely solely on the value of `errno` to determine if an error occurred. The function might have altered `errno`, but this does not ensure that `errno` will properly indicate an error condition. If the program does check `errno` after calling one of these functions, it should set `errno` to 0 before the function call.
+
+## Library Functions with Differing Standards Documentation
+
+Some functions behave differently regarding `errno` in various standards. The `fopen()` function is one such example. When `fopen()` encounters an error, it returns a null pointer. The C Standard makes no mention of `errno` when describing `fopen()`. However, POSIX.1 declares that when `fopen()` encounters an error, it returns a null pointer and sets `errno` to a value indicating the error \[[IEEE Std 1003.1-2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-IEEEStd1003.1-2013)\]. The implication is that a program conforming to C but not to POSIX (such as a Windows program) should not check `errno` after calling `fopen()`, but a POSIX program may check `errno` if `fopen()` returns a null pointer.
+
+## Library Functions and errno
+
+The following uses of `errno` are documented in the C Standard:
+
+* Functions defined in `` may set `errno` but are not required to.
+* For numeric conversion functions in the `strtod`, `strtol`, `wcstod`, and `wcstol` families, if the correct result is outside the range of representable values, an appropriate minimum or maximum value is returned and the value `ERANGE` is stored in `errno`. For floating-point conversion functions in the `strtod` and `wcstod` families, if an underflow occurs, whether `errno` acquires the value `ERANGE` is [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior). If the conversion fails, `0` is returned and `errno` is not set.
+* The numeric conversion function `atof()` and those in the `atoi` family "need not affect the value of" `errno`.
+* For mathematical functions in ``, if the integer expression `math_errhandling & MATH_ERRNO` is nonzero, on a domain error, `errno` acquires the value `EDOM`; on an overflow with default rounding or if the mathematical result is an exact infinity from finite arguments, `errno` acquires the value `ERANGE`; and on an underflow, whether `errno` acquires the value `ERANGE` is implementation-defined.
+* If a request made by calling `signal()` cannot be honored, a value of `SIG_ERR` is returned and a positive value is stored in `errno`.
+* The byte I/O functions, wide-character I/O functions, and multibyte conversion functions store the value of the macro `EILSEQ` in `errno` if and only if an encoding error occurs.
+* On failure, `fgetpos()` and `fsetpos()` return nonzero and store an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) positive value in `errno`.
+* On failure, `ftell()` returns `-1L` and stores an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) positive value in `errno`.
+* The `perror()` function maps the error number in `errno` to a message and writes it to `stderr`.
+The POSIX.1 standard defines the use of `errno` by many more functions (including the C standard library function). POSIX also has a small set of functions that are exceptions to the rule. These functions have no return value reserved to indicate an error, but they still set `errno` on error. To detect an error, an application must set `errno` to `0` before calling the function and check whether it is nonzero after the call. Affected functions include `strcoll()`, `strxfrm()`, `strerror()`, `wcscoll()`, `wcsxfrm()`, and `fwide()`. The C Standard allows these functions to set `errno` to a nonzero value on success. Consequently, this type of error checking should be performed only on POSIX systems.
+
+## Noncompliant Code Example (strtoul())
+
+This noncompliant code example fails to set `errno` to `0` before invoking `strtoul()`. If an error occurs, `strtoul()` returns a valid value (`ULONG_MAX`), so `errno` is the only means of determining if `strtoul()` ran successfully.
+
+```cpp
+#include
+#include
+#include
+
+void func(const char *c_str) {
+ unsigned long number;
+ char *endptr;
+
+ number = strtoul(c_str, &endptr, 0);
+ if (endptr == c_str || (number == ULONG_MAX
+ && errno == ERANGE)) {
+ /* Handle error */
+ } else {
+ /* Computation succeeded */
+ }
+}
+```
+Any error detected in this manner may have occurred earlier in the program or may not represent an actual error.
+
+## Compliant Solution (strtoul())
+
+This compliant solution sets `errno` to `0` before the call to `strtoul()` and inspects `errno` after the call:
+
+```cpp
+#include
+#include
+#include
+
+void func(const char *c_str) {
+ unsigned long number;
+ char *endptr;
+
+ errno = 0;
+ number = strtoul(c_str, &endptr, 0);
+ if (endptr == c_str || (number == ULONG_MAX
+ && errno == ERANGE)) {
+ /* Handle error */
+ } else {
+ /* Computation succeeded */
+ }
+}
+```
+
+## Noncompliant Code Example (ftell())
+
+This noncompliant code example, after calling `ftell()`, examines `errno` without first checking whether the out-of-band indicator returned by `ftell() `indicates an error.
+
+```cpp
+#include
+#include
+
+void func(FILE* fp) {
+ errno=0;
+ ftell(fp);
+ if (errno) {
+ perror("ftell");
+ }
+}
+```
+
+## Compliant Solution (ftell())
+
+This compliant solution first detects that `ftell() `failed using its out-of-band error indicator. Once an error has been confirmed, reading `errno` (implicitly by using the `perror()` function) is permitted.
+
+```cpp
+#include
+#include
+
+void func(FILE* fp) {
+ if (ftell(fp) == -1) {
+ perror("ftell");
+ }
+}
+```
+
+## Noncompliant Code Example (fopen())
+
+This noncompliant code example may fail to diagnose errors because `fopen()` might not set `errno` even if an error occurs:
+
+```cpp
+#include
+#include
+
+void func(const char *filename) {
+ FILE *fileptr;
+
+ errno = 0;
+ fileptr = fopen(filename, "rb");
+ if (errno != 0) {
+ /* Handle error */
+ }
+}
+```
+
+## Compliant Solution (fopen(), C)
+
+The C Standard makes no mention of `errno` when describing `fopen()`. In this compliant solution, the results of the call to `fopen()` are used to determine failure and `errno` is not checked:
+
+```cpp
+#include
+
+void func(const char *filename) {
+ FILE *fileptr = fopen(filename, "rb");
+ if (fileptr == NULL) {
+ /* An error occurred in fopen() */
+ }
+}
+```
+
+## Compliant Solution (fopen(), POSIX)
+
+In this compliant solution, `errno` is checked only after an error has already been detected by another means:
+
+```cpp
+#include
+#include
+
+void func(const char *filename) {
+ FILE *fileptr;
+
+ errno = 0;
+ fileptr = fopen(filename, "rb");
+ if (fileptr == NULL) {
+ /*
+ * An error occurred in fopen(); now it's valid
+ * to examine errno.
+ */
+ perror(filename);
+ }
+}
+```
+
+## Risk Assessment
+
+The improper use of `errno` may result in failing to detect an error condition or in incorrectly identifying an error condition when none exists.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ERR30-C | Medium | Probable | Medium | P8 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | errno-reset | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-ERR30 | Fully implemented |
CodeSonar | 7.1p0 | LANG.STRUCT.RC | Redundant Condition |
Compass/ROSE | | | Could detect violations of this rule by ensuring that each library function is accompanied by the proper treatment of errno |
Coverity | 2017.07 | MISRA C 2012 Rule 22.8 MISRA C 2012 Rule 22.9 MISRA C 2012 Rule 22.10 | Implemented |
Helix QAC | 2022.3 | C2500, C2501, C2502, C2503 C++3172, C++3173, C++3174, C++3175, C++3176, C++3177, C++3178, C++3179, C++3183, C++3184 | |
Klocwork | 2022.3 | CXX.ERRNO.NOT_SET CXX.ERRNO.NOT_CHECKED CXX.ERRNO.INCORRECTLY_CHECKED | |
LDRA tool suite | 9.7.1 | 111 D, 121 D, 122 D, 132 D, 134 D | Fully implemented |
Parasoft C/C++test | 2022.1 | CERT_C-ERR30-a CERT_C-ERR30-b | Properly use errno value Provide error handling for file opening errors right next to the call to fopen |
Polyspace Bug Finder | R2022b | CERT C: Rule ERR30-C | Checks for: Misuse of errnoisuse of errno, errno not resetrrno not reset. Rule fully covered. |
PRQA QA-C | 9.7 | 2500, 2501, 2502, 2503 | |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ERR30-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-456 and ERR30-C**
+
+CWE-456 = EXP33-C
+
+CWE-456 = Union( ERR30-C, list) where list =
+
+* Reading potentially uninitialized variables besides errno
+**CWE-248 and ERR30-C**
+
+Intersection( CWE-248, ERR30-C) = Ø
+
+CWE-248 is only for languages that support exceptions. It lists C++ and Java, but not C.
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ERR30-C: Take care when reading errno](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ERR30-C/ErrnoReadBeforeReturn.ql b/c/cert/src/rules/ERR30-C/ErrnoReadBeforeReturn.ql
new file mode 100644
index 0000000000..13f7e40303
--- /dev/null
+++ b/c/cert/src/rules/ERR30-C/ErrnoReadBeforeReturn.ql
@@ -0,0 +1,58 @@
+/**
+ * @id c/cert/errno-read-before-return
+ * @name ERR30-C: Do not check errno before the function return value
+ * @description Do not check errno before the function return value. Failing to do so might
+ * invalidate the error detection.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/err30-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.Errno
+import semmle.code.cpp.dataflow.DataFlow
+
+/**
+ * A call to an `OutOfBandErrnoSettingFunction`
+ */
+class OutOfBandErrnoSettingFunctionCertCall extends FunctionCall {
+ OutOfBandErrnoSettingFunctionCertCall() {
+ this.getTarget() instanceof OutOfBandErrnoSettingFunctionCert
+ }
+}
+
+/**
+ * A successor of an ErrnoSettingFunctionCertCall appearing
+ * before a check of the return value
+ */
+ControlFlowNode returnNotCheckedAfter(OutOfBandErrnoSettingFunctionCertCall errnoSet) {
+ result = errnoSet
+ or
+ exists(ControlFlowNode mid |
+ result = mid.getASuccessor() and
+ mid = returnNotCheckedAfter(errnoSet) and
+ // stop recursion on a return value check
+ not (
+ any(ControlStructure cs).getControllingExpr() = result and
+ DataFlow::localExprFlow(errnoSet, result.(Operation).getAnOperand*())
+ ) and
+ // stop recursion on a following errno setting function call
+ not result instanceof OutOfBandErrnoSettingFunctionCertCall
+ )
+}
+
+from OutOfBandErrnoSettingFunctionCertCall errnoSet, ErrnoRead check
+where
+ not isExcluded(check, Contracts4Package::errnoReadBeforeReturnQuery()) and
+ check = returnNotCheckedAfter(errnoSet)
+select check, "Do not read `errno` before checking the return value of function $@.", errnoSet,
+ errnoSet.toString()
diff --git a/c/cert/src/rules/ERR30-C/FunctionCallBeforeErrnoCheck.md b/c/cert/src/rules/ERR30-C/FunctionCallBeforeErrnoCheck.md
new file mode 100644
index 0000000000..5ec6577e45
--- /dev/null
+++ b/c/cert/src/rules/ERR30-C/FunctionCallBeforeErrnoCheck.md
@@ -0,0 +1,263 @@
+# ERR30-C: Do not call a function before checking errno
+
+This query implements the CERT-C rule ERR30-C:
+
+> Take care when reading errno
+
+
+## Description
+
+The value of `errno` is initialized to zero at program startup, but it is never subsequently set to zero by any C standard library function. The value of `errno` may be set to nonzero by a C standard library function call whether or not there is an error, provided the use of `errno` is not documented in the description of the function. It is meaningful for a program to inspect the contents of `errno` only after an error might have occurred. More precisely, `errno` is meaningful only after a library function that sets `errno` on error has returned an error code.
+
+According to Question 20.4 of C-FAQ \[[Summit 2005](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Summit05)\],
+
+> In general, you should detect errors by checking return values, and use `errno` only to distinguish among the various causes of an error, such as "File not found" or "Permission denied." (Typically, you use `perror` or `strerror` to print these discriminating error messages.) It's only necessary to detect errors with `errno` when a function does not have a unique, unambiguous, out-of-band error return (that is, because all of its possible return values are valid; one example is `atoi [*sic*]`). In these cases (and in these cases only; check the documentation to be sure whether a function allows this), you can detect errors by setting `errno` to 0, calling the function, and then testing `errno`. (Setting `errno` to 0 first is important, as no library function ever does that for you.)
+
+
+Note that `atoi()` is not required to set the value of `errno`.
+
+Library functions fall into the following categories:
+
+* Those that set `errno` and return an [out-of-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-out-of-banderrorindicator)
+* Those that set `errno` and return an [in-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-in-banderrorindicator)
+* Those that do not promise to set `errno`
+* Those with differing standards documentation
+
+## Library Functions that Set errno and Return an Out-of-Band Error Indicator
+
+The C Standard specifies that the functions listed in the following table set `errno` and return an [out-of-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-out-of-banderrorindicator). That is, their return value on error can never be returned by a successful call.
+
+A program may check `errno` after invoking these library functions but is not required to do so. The program should not check the value of `errno` without first verifying that the function returned an error indicator. For example, `errno` should not be checked after calling `signal()` without first ensuring that `signal()` actually returned `SIG_ERR`.
+
+**Functions That Set `errno` and Return an Out-of-Band Error Indicator**
+
+ Function Name | Return Value | errno Value |
ftell() | -1L | Positive |
fgetpos() , fsetpos() | Nonzero | Positive |
mbrtowc() , mbsrtowcs() | (size_t)(-1) | EILSEQ |
signal() | SIG_ERR | Positive |
wcrtomb() , wcsrtombs() | (size_t)(-1) | EILSEQ |
mbrtoc16() , mbrtoc32() | (size_t)(-1) | EILSEQ |
c16rtomb() , c32rtomb() | (size_t)(-1) | EILSEQ |
+
+
+## Library Functions that Set errno and Return an In-Band Error Indicator
+
+The C Standard specifies that the functions listed in the following table set `errno` and return an [in-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-in-banderrorindicator). That is, the return value when an error occurs is also a valid return value for successful calls. For example, the `strtoul()` function returns `ULONG_MAX` and sets `errno` to `ERANGE` if an error occurs. Because `ULONG_MAX` is a valid return value, the only way to confirm that an error occurred when LONG_MAX is returned is to check `errno`.
+
+The `fgetwc()` and `fputwc()` functions return `WEOF` in multiple cases, only one of which results in setting `errno`. The string conversion functions will return the maximum or minimum representable value and set `errno` to `ERANGE` if the converted value cannot be represented by the data type. However, if the conversion cannot happen because the input is invalid, the function will return `0`, and the output pointer parameter will be assigned the value of the input pointer parameter, provided the output parameter is non-null.
+
+A program that uses `errno` for error checking a function that returns an in-band error indicator must set `errno` to `0` before calling one of these library functions and then inspect `errno` before a subsequent library function call.
+
+**Functions that Set `errno` and Return an In-Band Error Indicator**
+
+ Function Name | Return Value | errno Value |
fgetwc() , fputwc() | WEOF | EILSEQ |
strtol() , wcstol() | LONG_MIN or LONG_MAX | ERANGE |
strtoll() , wcstoll() | LLONG_MIN or LLONG_MAX | ERANGE |
strtoul() , wcstoul() | ULONG_MAX | ERANGE |
strtoull() , wcstoull() | ULLONG_MAX | ERANGE |
strtoumax() , wcstoumax() | UINTMAX_MAX | ERANGE |
strtod() , wcstod() | 0 or ±HUGE_VAL | ERANGE |
strtof() , wcstof() | 0 or ±HUGE_VALF | ERANGE |
strtold() , wcstold() | 0 or ±HUGE_VALL | ERANGE |
strtoimax() , wcstoimax() | INTMAX_MIN , INTMAX_MAX | ERANGE |
+
+
+## Library Functions that Do Not Promise to Set errno
+
+The C Standard fails to document the behavior of `errno` for some functions. For example, the `setlocale()` function normally returns a null pointer in the event of an error, but no guarantees are made about setting `errno`.
+
+After calling one of these functions, a program should not rely solely on the value of `errno` to determine if an error occurred. The function might have altered `errno`, but this does not ensure that `errno` will properly indicate an error condition. If the program does check `errno` after calling one of these functions, it should set `errno` to 0 before the function call.
+
+## Library Functions with Differing Standards Documentation
+
+Some functions behave differently regarding `errno` in various standards. The `fopen()` function is one such example. When `fopen()` encounters an error, it returns a null pointer. The C Standard makes no mention of `errno` when describing `fopen()`. However, POSIX.1 declares that when `fopen()` encounters an error, it returns a null pointer and sets `errno` to a value indicating the error \[[IEEE Std 1003.1-2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-IEEEStd1003.1-2013)\]. The implication is that a program conforming to C but not to POSIX (such as a Windows program) should not check `errno` after calling `fopen()`, but a POSIX program may check `errno` if `fopen()` returns a null pointer.
+
+## Library Functions and errno
+
+The following uses of `errno` are documented in the C Standard:
+
+* Functions defined in `` may set `errno` but are not required to.
+* For numeric conversion functions in the `strtod`, `strtol`, `wcstod`, and `wcstol` families, if the correct result is outside the range of representable values, an appropriate minimum or maximum value is returned and the value `ERANGE` is stored in `errno`. For floating-point conversion functions in the `strtod` and `wcstod` families, if an underflow occurs, whether `errno` acquires the value `ERANGE` is [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior). If the conversion fails, `0` is returned and `errno` is not set.
+* The numeric conversion function `atof()` and those in the `atoi` family "need not affect the value of" `errno`.
+* For mathematical functions in ``, if the integer expression `math_errhandling & MATH_ERRNO` is nonzero, on a domain error, `errno` acquires the value `EDOM`; on an overflow with default rounding or if the mathematical result is an exact infinity from finite arguments, `errno` acquires the value `ERANGE`; and on an underflow, whether `errno` acquires the value `ERANGE` is implementation-defined.
+* If a request made by calling `signal()` cannot be honored, a value of `SIG_ERR` is returned and a positive value is stored in `errno`.
+* The byte I/O functions, wide-character I/O functions, and multibyte conversion functions store the value of the macro `EILSEQ` in `errno` if and only if an encoding error occurs.
+* On failure, `fgetpos()` and `fsetpos()` return nonzero and store an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) positive value in `errno`.
+* On failure, `ftell()` returns `-1L` and stores an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) positive value in `errno`.
+* The `perror()` function maps the error number in `errno` to a message and writes it to `stderr`.
+The POSIX.1 standard defines the use of `errno` by many more functions (including the C standard library function). POSIX also has a small set of functions that are exceptions to the rule. These functions have no return value reserved to indicate an error, but they still set `errno` on error. To detect an error, an application must set `errno` to `0` before calling the function and check whether it is nonzero after the call. Affected functions include `strcoll()`, `strxfrm()`, `strerror()`, `wcscoll()`, `wcsxfrm()`, and `fwide()`. The C Standard allows these functions to set `errno` to a nonzero value on success. Consequently, this type of error checking should be performed only on POSIX systems.
+
+## Noncompliant Code Example (strtoul())
+
+This noncompliant code example fails to set `errno` to `0` before invoking `strtoul()`. If an error occurs, `strtoul()` returns a valid value (`ULONG_MAX`), so `errno` is the only means of determining if `strtoul()` ran successfully.
+
+```cpp
+#include
+#include
+#include
+
+void func(const char *c_str) {
+ unsigned long number;
+ char *endptr;
+
+ number = strtoul(c_str, &endptr, 0);
+ if (endptr == c_str || (number == ULONG_MAX
+ && errno == ERANGE)) {
+ /* Handle error */
+ } else {
+ /* Computation succeeded */
+ }
+}
+```
+Any error detected in this manner may have occurred earlier in the program or may not represent an actual error.
+
+## Compliant Solution (strtoul())
+
+This compliant solution sets `errno` to `0` before the call to `strtoul()` and inspects `errno` after the call:
+
+```cpp
+#include
+#include
+#include
+
+void func(const char *c_str) {
+ unsigned long number;
+ char *endptr;
+
+ errno = 0;
+ number = strtoul(c_str, &endptr, 0);
+ if (endptr == c_str || (number == ULONG_MAX
+ && errno == ERANGE)) {
+ /* Handle error */
+ } else {
+ /* Computation succeeded */
+ }
+}
+```
+
+## Noncompliant Code Example (ftell())
+
+This noncompliant code example, after calling `ftell()`, examines `errno` without first checking whether the out-of-band indicator returned by `ftell() `indicates an error.
+
+```cpp
+#include
+#include
+
+void func(FILE* fp) {
+ errno=0;
+ ftell(fp);
+ if (errno) {
+ perror("ftell");
+ }
+}
+```
+
+## Compliant Solution (ftell())
+
+This compliant solution first detects that `ftell() `failed using its out-of-band error indicator. Once an error has been confirmed, reading `errno` (implicitly by using the `perror()` function) is permitted.
+
+```cpp
+#include
+#include
+
+void func(FILE* fp) {
+ if (ftell(fp) == -1) {
+ perror("ftell");
+ }
+}
+```
+
+## Noncompliant Code Example (fopen())
+
+This noncompliant code example may fail to diagnose errors because `fopen()` might not set `errno` even if an error occurs:
+
+```cpp
+#include
+#include
+
+void func(const char *filename) {
+ FILE *fileptr;
+
+ errno = 0;
+ fileptr = fopen(filename, "rb");
+ if (errno != 0) {
+ /* Handle error */
+ }
+}
+```
+
+## Compliant Solution (fopen(), C)
+
+The C Standard makes no mention of `errno` when describing `fopen()`. In this compliant solution, the results of the call to `fopen()` are used to determine failure and `errno` is not checked:
+
+```cpp
+#include
+
+void func(const char *filename) {
+ FILE *fileptr = fopen(filename, "rb");
+ if (fileptr == NULL) {
+ /* An error occurred in fopen() */
+ }
+}
+```
+
+## Compliant Solution (fopen(), POSIX)
+
+In this compliant solution, `errno` is checked only after an error has already been detected by another means:
+
+```cpp
+#include
+#include
+
+void func(const char *filename) {
+ FILE *fileptr;
+
+ errno = 0;
+ fileptr = fopen(filename, "rb");
+ if (fileptr == NULL) {
+ /*
+ * An error occurred in fopen(); now it's valid
+ * to examine errno.
+ */
+ perror(filename);
+ }
+}
+```
+
+## Risk Assessment
+
+The improper use of `errno` may result in failing to detect an error condition or in incorrectly identifying an error condition when none exists.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ERR30-C | Medium | Probable | Medium | P8 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | errno-reset | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-ERR30 | Fully implemented |
CodeSonar | 7.1p0 | LANG.STRUCT.RC | Redundant Condition |
Compass/ROSE | | | Could detect violations of this rule by ensuring that each library function is accompanied by the proper treatment of errno |
Coverity | 2017.07 | MISRA C 2012 Rule 22.8 MISRA C 2012 Rule 22.9 MISRA C 2012 Rule 22.10 | Implemented |
Helix QAC | 2022.3 | C2500, C2501, C2502, C2503 C++3172, C++3173, C++3174, C++3175, C++3176, C++3177, C++3178, C++3179, C++3183, C++3184 | |
Klocwork | 2022.3 | CXX.ERRNO.NOT_SET CXX.ERRNO.NOT_CHECKED CXX.ERRNO.INCORRECTLY_CHECKED | |
LDRA tool suite | 9.7.1 | 111 D, 121 D, 122 D, 132 D, 134 D | Fully implemented |
Parasoft C/C++test | 2022.1 | CERT_C-ERR30-a CERT_C-ERR30-b | Properly use errno value Provide error handling for file opening errors right next to the call to fopen |
Polyspace Bug Finder | R2022b | CERT C: Rule ERR30-C | Checks for: Misuse of errnoisuse of errno, errno not resetrrno not reset. Rule fully covered. |
PRQA QA-C | 9.7 | 2500, 2501, 2502, 2503 | |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ERR30-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-456 and ERR30-C**
+
+CWE-456 = EXP33-C
+
+CWE-456 = Union( ERR30-C, list) where list =
+
+* Reading potentially uninitialized variables besides errno
+**CWE-248 and ERR30-C**
+
+Intersection( CWE-248, ERR30-C) = Ø
+
+CWE-248 is only for languages that support exceptions. It lists C++ and Java, but not C.
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ERR30-C: Take care when reading errno](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ERR30-C/FunctionCallBeforeErrnoCheck.ql b/c/cert/src/rules/ERR30-C/FunctionCallBeforeErrnoCheck.ql
new file mode 100644
index 0000000000..8bf583faff
--- /dev/null
+++ b/c/cert/src/rules/ERR30-C/FunctionCallBeforeErrnoCheck.ql
@@ -0,0 +1,51 @@
+/**
+ * @id c/cert/function-call-before-errno-check
+ * @name ERR30-C: Do not call a function before checking errno
+ * @description After calling an errno-setting function, check errno before calling any other
+ * function. Failing to do so might end in errno being overwritten.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/err30-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.Errno
+
+/**
+ * A call to an `OutOfBandErrnoSettingFunction`
+ */
+class ErrnoSettingFunctionCall extends FunctionCall {
+ ErrnoSettingFunctionCall() { this.getTarget() instanceof InBandErrnoSettingFunction }
+}
+
+/**
+ * A successor of an ErrnoSettingFunctionCall appearing
+ * before a check of errno
+ */
+ControlFlowNode errnoNotCheckedAfter(ErrnoSettingFunctionCall errnoSet) {
+ result = errnoSet
+ or
+ exists(ControlFlowNode mid |
+ result = mid.getASuccessor() and
+ mid = errnoNotCheckedAfter(errnoSet) and
+ // stop recursion on an error check
+ not result instanceof ErrnoRead
+ )
+}
+
+from ErrnoSettingFunctionCall errnoSet, FunctionCall fc
+where
+ not isExcluded(fc, Contracts4Package::functionCallBeforeErrnoCheckQuery()) and
+ fc != errnoSet and
+ fc = errnoNotCheckedAfter(errnoSet)
+select errnoSet,
+ "The value of `errno` is not checked after this call to `" + errnoSet.getTarget().getName() + "`."
diff --git a/c/cert/src/rules/ERR30-C/SetlocaleMightSetErrno.md b/c/cert/src/rules/ERR30-C/SetlocaleMightSetErrno.md
new file mode 100644
index 0000000000..e4b0a0e067
--- /dev/null
+++ b/c/cert/src/rules/ERR30-C/SetlocaleMightSetErrno.md
@@ -0,0 +1,263 @@
+# ERR30-C: Do not rely solely on errno to determine if en error occurred in setlocale
+
+This query implements the CERT-C rule ERR30-C:
+
+> Take care when reading errno
+
+
+## Description
+
+The value of `errno` is initialized to zero at program startup, but it is never subsequently set to zero by any C standard library function. The value of `errno` may be set to nonzero by a C standard library function call whether or not there is an error, provided the use of `errno` is not documented in the description of the function. It is meaningful for a program to inspect the contents of `errno` only after an error might have occurred. More precisely, `errno` is meaningful only after a library function that sets `errno` on error has returned an error code.
+
+According to Question 20.4 of C-FAQ \[[Summit 2005](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Summit05)\],
+
+> In general, you should detect errors by checking return values, and use `errno` only to distinguish among the various causes of an error, such as "File not found" or "Permission denied." (Typically, you use `perror` or `strerror` to print these discriminating error messages.) It's only necessary to detect errors with `errno` when a function does not have a unique, unambiguous, out-of-band error return (that is, because all of its possible return values are valid; one example is `atoi [*sic*]`). In these cases (and in these cases only; check the documentation to be sure whether a function allows this), you can detect errors by setting `errno` to 0, calling the function, and then testing `errno`. (Setting `errno` to 0 first is important, as no library function ever does that for you.)
+
+
+Note that `atoi()` is not required to set the value of `errno`.
+
+Library functions fall into the following categories:
+
+* Those that set `errno` and return an [out-of-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-out-of-banderrorindicator)
+* Those that set `errno` and return an [in-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-in-banderrorindicator)
+* Those that do not promise to set `errno`
+* Those with differing standards documentation
+
+## Library Functions that Set errno and Return an Out-of-Band Error Indicator
+
+The C Standard specifies that the functions listed in the following table set `errno` and return an [out-of-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-out-of-banderrorindicator). That is, their return value on error can never be returned by a successful call.
+
+A program may check `errno` after invoking these library functions but is not required to do so. The program should not check the value of `errno` without first verifying that the function returned an error indicator. For example, `errno` should not be checked after calling `signal()` without first ensuring that `signal()` actually returned `SIG_ERR`.
+
+**Functions That Set `errno` and Return an Out-of-Band Error Indicator**
+
+ Function Name | Return Value | errno Value |
ftell() | -1L | Positive |
fgetpos() , fsetpos() | Nonzero | Positive |
mbrtowc() , mbsrtowcs() | (size_t)(-1) | EILSEQ |
signal() | SIG_ERR | Positive |
wcrtomb() , wcsrtombs() | (size_t)(-1) | EILSEQ |
mbrtoc16() , mbrtoc32() | (size_t)(-1) | EILSEQ |
c16rtomb() , c32rtomb() | (size_t)(-1) | EILSEQ |
+
+
+## Library Functions that Set errno and Return an In-Band Error Indicator
+
+The C Standard specifies that the functions listed in the following table set `errno` and return an [in-band error indicator](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-in-banderrorindicator). That is, the return value when an error occurs is also a valid return value for successful calls. For example, the `strtoul()` function returns `ULONG_MAX` and sets `errno` to `ERANGE` if an error occurs. Because `ULONG_MAX` is a valid return value, the only way to confirm that an error occurred when LONG_MAX is returned is to check `errno`.
+
+The `fgetwc()` and `fputwc()` functions return `WEOF` in multiple cases, only one of which results in setting `errno`. The string conversion functions will return the maximum or minimum representable value and set `errno` to `ERANGE` if the converted value cannot be represented by the data type. However, if the conversion cannot happen because the input is invalid, the function will return `0`, and the output pointer parameter will be assigned the value of the input pointer parameter, provided the output parameter is non-null.
+
+A program that uses `errno` for error checking a function that returns an in-band error indicator must set `errno` to `0` before calling one of these library functions and then inspect `errno` before a subsequent library function call.
+
+**Functions that Set `errno` and Return an In-Band Error Indicator**
+
+ Function Name | Return Value | errno Value |
fgetwc() , fputwc() | WEOF | EILSEQ |
strtol() , wcstol() | LONG_MIN or LONG_MAX | ERANGE |
strtoll() , wcstoll() | LLONG_MIN or LLONG_MAX | ERANGE |
strtoul() , wcstoul() | ULONG_MAX | ERANGE |
strtoull() , wcstoull() | ULLONG_MAX | ERANGE |
strtoumax() , wcstoumax() | UINTMAX_MAX | ERANGE |
strtod() , wcstod() | 0 or ±HUGE_VAL | ERANGE |
strtof() , wcstof() | 0 or ±HUGE_VALF | ERANGE |
strtold() , wcstold() | 0 or ±HUGE_VALL | ERANGE |
strtoimax() , wcstoimax() | INTMAX_MIN , INTMAX_MAX | ERANGE |
+
+
+## Library Functions that Do Not Promise to Set errno
+
+The C Standard fails to document the behavior of `errno` for some functions. For example, the `setlocale()` function normally returns a null pointer in the event of an error, but no guarantees are made about setting `errno`.
+
+After calling one of these functions, a program should not rely solely on the value of `errno` to determine if an error occurred. The function might have altered `errno`, but this does not ensure that `errno` will properly indicate an error condition. If the program does check `errno` after calling one of these functions, it should set `errno` to 0 before the function call.
+
+## Library Functions with Differing Standards Documentation
+
+Some functions behave differently regarding `errno` in various standards. The `fopen()` function is one such example. When `fopen()` encounters an error, it returns a null pointer. The C Standard makes no mention of `errno` when describing `fopen()`. However, POSIX.1 declares that when `fopen()` encounters an error, it returns a null pointer and sets `errno` to a value indicating the error \[[IEEE Std 1003.1-2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-IEEEStd1003.1-2013)\]. The implication is that a program conforming to C but not to POSIX (such as a Windows program) should not check `errno` after calling `fopen()`, but a POSIX program may check `errno` if `fopen()` returns a null pointer.
+
+## Library Functions and errno
+
+The following uses of `errno` are documented in the C Standard:
+
+* Functions defined in `` may set `errno` but are not required to.
+* For numeric conversion functions in the `strtod`, `strtol`, `wcstod`, and `wcstol` families, if the correct result is outside the range of representable values, an appropriate minimum or maximum value is returned and the value `ERANGE` is stored in `errno`. For floating-point conversion functions in the `strtod` and `wcstod` families, if an underflow occurs, whether `errno` acquires the value `ERANGE` is [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior). If the conversion fails, `0` is returned and `errno` is not set.
+* The numeric conversion function `atof()` and those in the `atoi` family "need not affect the value of" `errno`.
+* For mathematical functions in ``, if the integer expression `math_errhandling & MATH_ERRNO` is nonzero, on a domain error, `errno` acquires the value `EDOM`; on an overflow with default rounding or if the mathematical result is an exact infinity from finite arguments, `errno` acquires the value `ERANGE`; and on an underflow, whether `errno` acquires the value `ERANGE` is implementation-defined.
+* If a request made by calling `signal()` cannot be honored, a value of `SIG_ERR` is returned and a positive value is stored in `errno`.
+* The byte I/O functions, wide-character I/O functions, and multibyte conversion functions store the value of the macro `EILSEQ` in `errno` if and only if an encoding error occurs.
+* On failure, `fgetpos()` and `fsetpos()` return nonzero and store an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) positive value in `errno`.
+* On failure, `ftell()` returns `-1L` and stores an [implementation-defined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation-definedbehavior) positive value in `errno`.
+* The `perror()` function maps the error number in `errno` to a message and writes it to `stderr`.
+The POSIX.1 standard defines the use of `errno` by many more functions (including the C standard library function). POSIX also has a small set of functions that are exceptions to the rule. These functions have no return value reserved to indicate an error, but they still set `errno` on error. To detect an error, an application must set `errno` to `0` before calling the function and check whether it is nonzero after the call. Affected functions include `strcoll()`, `strxfrm()`, `strerror()`, `wcscoll()`, `wcsxfrm()`, and `fwide()`. The C Standard allows these functions to set `errno` to a nonzero value on success. Consequently, this type of error checking should be performed only on POSIX systems.
+
+## Noncompliant Code Example (strtoul())
+
+This noncompliant code example fails to set `errno` to `0` before invoking `strtoul()`. If an error occurs, `strtoul()` returns a valid value (`ULONG_MAX`), so `errno` is the only means of determining if `strtoul()` ran successfully.
+
+```cpp
+#include
+#include
+#include
+
+void func(const char *c_str) {
+ unsigned long number;
+ char *endptr;
+
+ number = strtoul(c_str, &endptr, 0);
+ if (endptr == c_str || (number == ULONG_MAX
+ && errno == ERANGE)) {
+ /* Handle error */
+ } else {
+ /* Computation succeeded */
+ }
+}
+```
+Any error detected in this manner may have occurred earlier in the program or may not represent an actual error.
+
+## Compliant Solution (strtoul())
+
+This compliant solution sets `errno` to `0` before the call to `strtoul()` and inspects `errno` after the call:
+
+```cpp
+#include
+#include
+#include
+
+void func(const char *c_str) {
+ unsigned long number;
+ char *endptr;
+
+ errno = 0;
+ number = strtoul(c_str, &endptr, 0);
+ if (endptr == c_str || (number == ULONG_MAX
+ && errno == ERANGE)) {
+ /* Handle error */
+ } else {
+ /* Computation succeeded */
+ }
+}
+```
+
+## Noncompliant Code Example (ftell())
+
+This noncompliant code example, after calling `ftell()`, examines `errno` without first checking whether the out-of-band indicator returned by `ftell() `indicates an error.
+
+```cpp
+#include
+#include
+
+void func(FILE* fp) {
+ errno=0;
+ ftell(fp);
+ if (errno) {
+ perror("ftell");
+ }
+}
+```
+
+## Compliant Solution (ftell())
+
+This compliant solution first detects that `ftell() `failed using its out-of-band error indicator. Once an error has been confirmed, reading `errno` (implicitly by using the `perror()` function) is permitted.
+
+```cpp
+#include
+#include
+
+void func(FILE* fp) {
+ if (ftell(fp) == -1) {
+ perror("ftell");
+ }
+}
+```
+
+## Noncompliant Code Example (fopen())
+
+This noncompliant code example may fail to diagnose errors because `fopen()` might not set `errno` even if an error occurs:
+
+```cpp
+#include
+#include
+
+void func(const char *filename) {
+ FILE *fileptr;
+
+ errno = 0;
+ fileptr = fopen(filename, "rb");
+ if (errno != 0) {
+ /* Handle error */
+ }
+}
+```
+
+## Compliant Solution (fopen(), C)
+
+The C Standard makes no mention of `errno` when describing `fopen()`. In this compliant solution, the results of the call to `fopen()` are used to determine failure and `errno` is not checked:
+
+```cpp
+#include
+
+void func(const char *filename) {
+ FILE *fileptr = fopen(filename, "rb");
+ if (fileptr == NULL) {
+ /* An error occurred in fopen() */
+ }
+}
+```
+
+## Compliant Solution (fopen(), POSIX)
+
+In this compliant solution, `errno` is checked only after an error has already been detected by another means:
+
+```cpp
+#include
+#include
+
+void func(const char *filename) {
+ FILE *fileptr;
+
+ errno = 0;
+ fileptr = fopen(filename, "rb");
+ if (fileptr == NULL) {
+ /*
+ * An error occurred in fopen(); now it's valid
+ * to examine errno.
+ */
+ perror(filename);
+ }
+}
+```
+
+## Risk Assessment
+
+The improper use of `errno` may result in failing to detect an error condition or in incorrectly identifying an error condition when none exists.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ERR30-C | Medium | Probable | Medium | P8 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | errno-reset | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-ERR30 | Fully implemented |
CodeSonar | 7.1p0 | LANG.STRUCT.RC | Redundant Condition |
Compass/ROSE | | | Could detect violations of this rule by ensuring that each library function is accompanied by the proper treatment of errno |
Coverity | 2017.07 | MISRA C 2012 Rule 22.8 MISRA C 2012 Rule 22.9 MISRA C 2012 Rule 22.10 | Implemented |
Helix QAC | 2022.3 | C2500, C2501, C2502, C2503 C++3172, C++3173, C++3174, C++3175, C++3176, C++3177, C++3178, C++3179, C++3183, C++3184 | |
Klocwork | 2022.3 | CXX.ERRNO.NOT_SET CXX.ERRNO.NOT_CHECKED CXX.ERRNO.INCORRECTLY_CHECKED | |
LDRA tool suite | 9.7.1 | 111 D, 121 D, 122 D, 132 D, 134 D | Fully implemented |
Parasoft C/C++test | 2022.1 | CERT_C-ERR30-a CERT_C-ERR30-b | Properly use errno value Provide error handling for file opening errors right next to the call to fopen |
Polyspace Bug Finder | R2022b | CERT C: Rule ERR30-C | Checks for: Misuse of errnoisuse of errno, errno not resetrrno not reset. Rule fully covered. |
PRQA QA-C | 9.7 | 2500, 2501, 2502, 2503 | |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ERR30-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-456 and ERR30-C**
+
+CWE-456 = EXP33-C
+
+CWE-456 = Union( ERR30-C, list) where list =
+
+* Reading potentially uninitialized variables besides errno
+**CWE-248 and ERR30-C**
+
+Intersection( CWE-248, ERR30-C) = Ø
+
+CWE-248 is only for languages that support exceptions. It lists C++ and Java, but not C.
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [ERR30-C: Take care when reading errno](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ERR30-C/SetlocaleMightSetErrno.ql b/c/cert/src/rules/ERR30-C/SetlocaleMightSetErrno.ql
new file mode 100644
index 0000000000..a7ccf8c041
--- /dev/null
+++ b/c/cert/src/rules/ERR30-C/SetlocaleMightSetErrno.ql
@@ -0,0 +1,98 @@
+/**
+ * @id c/cert/setlocale-might-set-errno
+ * @name ERR30-C: Do not rely solely on errno to determine if en error occurred in setlocale
+ * @description Do not rely solely on errno to determine if en error occurred in setlocale.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/err30-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.Errno
+import semmle.code.cpp.dataflow.DataFlow
+
+class SetlocaleFunctionCall extends FunctionCall {
+ SetlocaleFunctionCall() { this.getTarget().hasGlobalName("setlocale") }
+}
+
+/**
+ * An `errno` read after setlocale
+ */
+ControlFlowNode errnoChecked(SetlocaleFunctionCall setlocale) {
+ result = setlocale
+ or
+ exists(ControlFlowNode mid |
+ result = mid.getASuccessor() and
+ mid = errnoChecked(setlocale) and
+ // stop recursion on a following errno-setting function call
+ not result instanceof OutOfBandErrnoSettingFunctionCert and
+ not result instanceof InBandErrnoSettingFunction
+ )
+}
+
+/**
+ * CFG nodes preceding a call to setlocale
+ */
+ControlFlowNode notZeroedPriorToSetlocale(SetlocaleFunctionCall fc) {
+ result = fc
+ or
+ exists(ControlFlowNode mid |
+ result = mid.getAPredecessor() and
+ mid = notZeroedPriorToSetlocale(fc) and
+ // stop recursion when `errno` is set to zero
+ not result instanceof ErrnoZeroed and
+ not result = any(ErrnoGuard g).getZeroedSuccessor()
+ )
+}
+
+/**
+ * A successor of a `setlocale` call appearing
+ * before a check of the return value
+ */
+ControlFlowNode returnNotCheckedAfter(SetlocaleFunctionCall setlocale) {
+ result = setlocale
+ or
+ exists(ControlFlowNode mid |
+ result = mid.getASuccessor() and
+ mid = returnNotCheckedAfter(setlocale) and
+ // stop recursion on a return value check
+ not (
+ any(ControlStructure cs).getControllingExpr() = result and
+ DataFlow::localExprFlow(setlocale, result.(Operation).getAnOperand*())
+ ) and
+ // stop recursion on a following errno setting function call
+ not result instanceof SetlocaleFunctionCall
+ )
+}
+
+from SetlocaleFunctionCall setlocale, ErrnoRead check, string msg
+where
+ not isExcluded(setlocale, Contracts4Package::setlocaleMightSetErrnoQuery()) and
+ // errno is checked after setlocale
+ check = errnoChecked(setlocale) and
+ (
+ // errno is not set to zero before the call
+ exists(ControlFlowNode cause | cause = notZeroedPriorToSetlocale(setlocale) |
+ // `errno` is not reset anywhere in the function
+ cause = setlocale.getEnclosingFunction().getBlock()
+ or
+ // `errno` is not reset after a call to a function
+ cause = any(FunctionCall fc2 | fc2 != setlocale)
+ ) and
+ msg =
+ "The value of `errno` may be different than `0` when `setlocale` is called. The following `errno` check might be invalid."
+ or
+ //errno is checked before the return value
+ check = returnNotCheckedAfter(setlocale) and
+ msg = "Do not read `errno` before checking the return value of a call to `setlocale`."
+ )
+select setlocale, msg
diff --git a/c/cert/src/rules/ERR32-C/DoNotRelyOnIndeterminateValuesOfErrno.md b/c/cert/src/rules/ERR32-C/DoNotRelyOnIndeterminateValuesOfErrno.md
new file mode 100644
index 0000000000..614bc9a5f4
--- /dev/null
+++ b/c/cert/src/rules/ERR32-C/DoNotRelyOnIndeterminateValuesOfErrno.md
@@ -0,0 +1,207 @@
+# ERR32-C: Do not rely on indeterminate values of errno
+
+This query implements the CERT-C rule ERR32-C:
+
+> Do not rely on indeterminate values of errno
+
+
+## Description
+
+According to the C Standard \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], the behavior of a program is [undefined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) when
+
+> the value of `errno` is referred to after a signal occurred other than as the result of calling the `abort` or `raise` function and the corresponding signal handler obtained a `SIG_ERR` return from a call to the `signal` function.
+
+
+See [undefined behavior 133](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_133).
+
+A signal handler is allowed to call `signal();` if that fails, `signal()` returns `SIG_ERR` and sets `errno` to a positive value. However, if the event that caused a signal was external (not the result of the program calling `abort()` or `raise()`), the only functions the signal handler may call are `_Exit()` or `abort()`, or it may call `signal()` on the signal currently being handled; if `signal()` fails, the value of `errno` is [indeterminate](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue).
+
+This rule is also a special case of [SIG31-C. Do not access shared objects in signal handlers](https://wiki.sei.cmu.edu/confluence/display/c/SIG31-C.+Do+not+access+shared+objects+in+signal+handlers). The object designated by `errno` is of static storage duration and is not a `volatile sig_atomic_t`. As a result, performing any action that would require `errno` to be set would normally cause [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). The C Standard, 7.14.1.1, paragraph 5, makes a special exception for `errno` in this case, allowing `errno` to take on an indeterminate value but specifying that there is no other [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). This special exception makes it possible to call `signal()` from within a signal handler without risking [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior), but the handler, and any code executed after the handler returns, must not depend on the value of `errno` being meaningful.
+
+## Noncompliant Code Example
+
+The `handler()` function in this noncompliant code example attempts to restore default handling for the signal indicated by `signum`. If the request to set the signal to default can be honored, the `signal()` function returns the value of the signal handler for the most recent successful call to the `signal()` function for the specified signal. Otherwise, a value of `SIG_ERR` is returned and a positive value is stored in `errno`. Unfortunately, the value of `errno` is indeterminate because the `handler()` function is called when an external signal is raised, so any attempt to read `errno` (for example, by the `perror()` function) is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior):
+
+```cpp
+#include
+#include
+#include
+
+typedef void (*pfv)(int);
+
+void handler(int signum) {
+ pfv old_handler = signal(signum, SIG_DFL);
+ if (old_handler == SIG_ERR) {
+ perror("SIGINT handler"); /* Undefined behavior */
+ /* Handle error */
+ }
+}
+
+int main(void) {
+ pfv old_handler = signal(SIGINT, handler);
+ if (old_handler == SIG_ERR) {
+ perror("SIGINT handler");
+ /* Handle error */
+ }
+
+ /* Main code loop */
+
+ return EXIT_SUCCESS;
+}
+
+```
+The call to `perror()` from `handler()` also violates [SIG30-C. Call only asynchronous-safe functions within signal handlers](https://wiki.sei.cmu.edu/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers).
+
+## Compliant Solution
+
+This compliant solution does not reference `errno` and does not return from the signal handler if the `signal()` call fails:
+
+```cpp
+#include
+#include
+#include
+
+typedef void (*pfv)(int);
+
+void handler(int signum) {
+ pfv old_handler = signal(signum, SIG_DFL);
+ if (old_handler == SIG_ERR) {
+ abort();
+ }
+}
+
+int main(void) {
+ pfv old_handler = signal(SIGINT, handler);
+ if (old_handler == SIG_ERR) {
+ perror("SIGINT handler");
+ /* Handle error */
+ }
+
+ /* Main code loop */
+
+ return EXIT_SUCCESS;
+}
+
+```
+
+## Noncompliant Code Example (POSIX)
+
+POSIX is less restrictive than C about what applications can do in signal handlers. It has a long list of [asynchronous-safe](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-asynchronous-safe) functions that can be called. (See [SIG30-C. Call only asynchronous-safe functions within signal handlers](https://wiki.sei.cmu.edu/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers).) Many of these functions set `errno` on error, which can lead to a signal handler being executed between a call to a failed function and the subsequent inspection of `errno`. Consequently, the value inspected is not the one set by that function but the one set by a function call in the signal handler. POSIX applications can avoid this problem by ensuring that signal handlers containing code that might alter `errno`; always save the value of `errno` on entry and restore it before returning.
+
+The signal handler in this noncompliant code example alters the value of `errno`. As a result, it can cause incorrect error handling if executed between a failed function call and the subsequent inspection of `errno`:
+
+```cpp
+#include
+#include
+#include
+#include
+
+void reaper(int signum) {
+ errno = 0;
+ for (;;) {
+ int rc = waitpid(-1, NULL, WNOHANG);
+ if ((0 == rc) || (-1 == rc && EINTR != errno)) {
+ break;
+ }
+ }
+ if (ECHILD != errno) {
+ /* Handle error */
+ }
+}
+
+int main(void) {
+ struct sigaction act;
+ act.sa_handler = reaper;
+ act.sa_flags = 0;
+ if (sigemptyset(&act.sa_mask) != 0) {
+ /* Handle error */
+ }
+ if (sigaction(SIGCHLD, &act, NULL) != 0) {
+ /* Handle error */
+ }
+
+ /* ... */
+
+ return EXIT_SUCCESS;
+}
+
+```
+
+## Compliant Solution (POSIX)
+
+This compliant solution saves and restores the value of `errno` in the signal handler:
+
+```cpp
+#include
+#include
+#include
+#include
+
+void reaper(int signum) {
+ errno_t save_errno = errno;
+ errno = 0;
+ for (;;) {
+ int rc = waitpid(-1, NULL, WNOHANG);
+ if ((0 == rc) || (-1 == rc && EINTR != errno)) {
+ break;
+ }
+ }
+ if (ECHILD != errno) {
+ /* Handle error */
+ }
+ errno = save_errno;
+}
+
+int main(void) {
+ struct sigaction act;
+ act.sa_handler = reaper;
+ act.sa_flags = 0;
+ if (sigemptyset(&act.sa_mask) != 0) {
+ /* Handle error */
+ }
+ if (sigaction(SIGCHLD, &act, NULL) != 0) {
+ /* Handle error */
+ }
+
+ /* ... */
+
+ return EXIT_SUCCESS;
+}
+
+```
+
+## Risk Assessment
+
+Referencing indeterminate values of `errno` is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior).
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ERR32-C | Low | Unlikely | Low | P3 | L3 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Axivion Bauhaus Suite | 7.2.0 | CertC-ERR32 | |
Compass/ROSE | | | Could detect violations of this rule by looking for signal handlers that themselves call signal() . A violation is reported if the call fails and the handler therefore checks errno . A violation also exists if the signal handler modifies errno without first copying its value elsewhere |
Coverity | 2017.07 | MISRA C 2012 Rule 22.8 MISRA C 2012 Rule 22.9 MISRA C 2012 Rule 22.10 | Implemented |
Helix QAC | 2022.3 | C2031, C4781, C4782, C4783 C++4781, C++4782, C++4783 | |
Klocwork | 2022.3 | MISRA.INCL.SIGNAL.2012 MISRA.STDLIB.SIGNAL | |
LDRA tool suite | 9.7.1 | 44 S | Enhanced enforcement |
Parasoft C/C++test | 2022.1 | CERT_C-ERR32-a | Properly use errno value |
Polyspace Bug Finder | R2022b | CERT C: Rule ERR32-C | Checks for misuse of errno in a signal handler (rule fully covered) |
PRQA QA-C | 9.7 | 2031 | |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ERR32-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+The rule is enforced in the context of a single function.
+
+## References
+
+* CERT-C: [ERR32-C: Do not rely on indeterminate values of errno](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ERR32-C/DoNotRelyOnIndeterminateValuesOfErrno.ql b/c/cert/src/rules/ERR32-C/DoNotRelyOnIndeterminateValuesOfErrno.ql
new file mode 100644
index 0000000000..146d0cb30f
--- /dev/null
+++ b/c/cert/src/rules/ERR32-C/DoNotRelyOnIndeterminateValuesOfErrno.ql
@@ -0,0 +1,95 @@
+/**
+ * @id c/cert/do-not-rely-on-indeterminate-values-of-errno
+ * @name ERR32-C: Do not rely on indeterminate values of errno
+ * @description Do not rely on indeterminate values of errno. This may result in undefined behavior.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/err32-c
+ * correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/low
+ * external/cert/priority/p3
+ * external/cert/level/l3
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.Errno
+import codingstandards.c.Signal
+import semmle.code.cpp.controlflow.Guards
+import semmle.code.cpp.dataflow.DataFlow
+
+/**
+ * A check on `signal` call return value
+ * `if (signal(SIGINT, handler) == SIG_ERR)`
+ */
+class SignalCheckOperation extends EqualityOperation instanceof GuardCondition {
+ BasicBlock errorSuccessor;
+
+ SignalCheckOperation() {
+ this.getAnOperand() = any(MacroInvocation m | m.getMacroName() = "SIG_ERR").getExpr() and
+ (
+ this.getOperator() = "==" and
+ super.controls(errorSuccessor, true)
+ or
+ this.getOperator() = "!=" and
+ super.controls(errorSuccessor, false)
+ )
+ }
+
+ BasicBlock getCheckedSuccessor() { result != errorSuccessor and result = this.getASuccessor() }
+
+ BasicBlock getErrorSuccessor() { result = errorSuccessor }
+}
+
+/**
+ * Models signal handlers that call signal() and return
+ */
+class SignalCallingHandler extends SignalHandler {
+ SignalCallingHandler() {
+ // calls signal() on the handled signal
+ exists(SignalCall sCall |
+ sCall.getEnclosingFunction() = this and
+ DataFlow::localFlow(DataFlow::parameterNode(this.getParameter(0)),
+ DataFlow::exprNode(sCall.getArgument(0))) and
+ // does not abort on error
+ not exists(SignalCheckOperation sCheck, AbortCall abort |
+ DataFlow::localExprFlow(sCall, sCheck.getAnOperand()) and
+ abort = sCheck.getErrorSuccessor().(BlockStmt).getStmt(0).(ExprStmt).getExpr()
+ )
+ )
+ }
+}
+
+/**
+ * CFG nodes preceeding `ErrnoRead`
+ */
+ControlFlowNode preceedErrnoRead(ErrnoRead er) {
+ result = er
+ or
+ exists(ControlFlowNode mid |
+ result = mid.getAPredecessor() and
+ mid = preceedErrnoRead(er) and
+ // stop recursion on calls to `abort` and `_Exit`
+ not result instanceof AbortCall and
+ // stop recursion on successful `SignalCheckOperation`
+ not result = any(SignalCheckOperation o).getCheckedSuccessor()
+ )
+}
+
+from ErrnoRead errno, SignalCall signal
+where
+ not isExcluded(errno, Contracts5Package::doNotRelyOnIndeterminateValuesOfErrnoQuery()) and
+ exists(SignalCallingHandler handler |
+ // errno read after the handler returns
+ handler.getRegistration() = signal
+ or
+ // errno read inside the handler
+ signal.getEnclosingFunction() = handler
+ |
+ signal = preceedErrnoRead(errno)
+ )
+select errno, "`errno` has indeterminate value after this $@.", signal, signal.toString()
diff --git a/c/cert/src/rules/ERR33-C/DetectAndHandleStandardLibraryErrors.md b/c/cert/src/rules/ERR33-C/DetectAndHandleStandardLibraryErrors.md
new file mode 100644
index 0000000000..64c7d0a7f0
--- /dev/null
+++ b/c/cert/src/rules/ERR33-C/DetectAndHandleStandardLibraryErrors.md
@@ -0,0 +1,379 @@
+# ERR33-C: Detect and handle standard library errors
+
+This query implements the CERT-C rule ERR33-C:
+
+> Detect and handle standard library errors
+
+
+## Description
+
+The majority of the standard library functions, including I/O functions and memory allocation functions, return either a valid value or a value of the correct return type that indicates an error (for example, −1 or a null pointer). Assuming that all calls to such functions will succeed and failing to check the return value for an indication of an error is a dangerous practice that may lead to [unexpected](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-unexpectedbehavior) or [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) when an error occurs. It is essential that programs detect and appropriately handle all errors in accordance with an error-handling policy.
+
+The successful completion or failure of each of the standard library functions listed in the following table shall be determined either by comparing the function’s return value with the value listed in the column labeled “Error Return” or by calling one of the library functions mentioned in the footnotes.
+
+**Standard Library Functions**
+
+ Function | Successful Return | Error Return |
aligned_alloc() | Pointer to space | NULL |
asctime_s() | 0 | Nonzero |
at_quick_exit() | 0 | Nonzero |
atexit() | 0 | Nonzero |
bsearch() | Pointer to matching element | NULL |
bsearch_s() | Pointer to matching element | NULL |
btowc() | Converted wide character | WEOF |
c16rtomb() | Number of bytes | (size_t)(-1) |
c32rtomb() | Number of bytes | (size_t)(-1) |
calloc() | Pointer to space | NULL |
clock() | Processor time | (clock_t)(-1) |
cnd_broadcast() | thrd_success | thrd_error |
cnd_init() | thrd_success | thrd_nomem or thrd_error |
cnd_signal() | thrd_success | thrd_error |
cnd_timedwait() | thrd_success | thrd_timedout or thrd_error |
cnd_wait() | thrd_success | thrd_error |
ctime_s() | 0 | Nonzero |
fclose() | 0 | EOF (negative) |
fflush() | 0 | EOF (negative) |
fgetc() | Character read | EOF 1 |
fgetpos() | 0 | Nonzero, errno > 0 |
fgets() | Pointer to string | NULL |
fgetwc() | Wide character read | WEOF 1 |
fopen() | Pointer to stream | NULL |
fopen_s() | 0 | Nonzero |
fprintf() | Number of characters (nonnegative) | Negative |
fprintf_s() | Number of characters (nonnegative) | Negative |
fputc() | Character written | EOF 2 |
fputs() | Nonnegative | EOF (negative) |
fputwc() | Wide character written | WEOF |
fputws() | Nonnegative | EOF (negative) |
fread() | Elements read | Elements read |
freopen() | Pointer to stream | NULL |
freopen_s() | 0 | Nonzero |
fscanf() | Number of conversions (nonnegative) | EOF (negative) |
fscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
fseek() | 0 | Nonzero |
fsetpos() | 0 | Nonzero, errno > 0 |
ftell() | File position | −1L , errno > 0 |
fwprintf() | Number of wide characters (nonnegative) | Negative |
fwprintf_s() | Number of wide characters (nonnegative) | Negative |
fwrite() | Elements written | Elements written |
fwscanf() | Number of conversions (nonnegative) | EOF (negative) |
fwscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
getc() | Character read | EOF 1 |
getchar() | Character read | EOF 1 |
getenv() | Pointer to string | NULL |
getenv_s() | Pointer to string | NULL |
gets_s() | Pointer to string | NULL |
getwc() | Wide character read | WEOF |
getwchar() | Wide character read | WEOF |
gmtime() | Pointer to broken-down time | NULL |
gmtime_s() | Pointer to broken-down time | NULL |
localtime() | Pointer to broken-down time | NULL |
localtime_s() | Pointer to broken-down time | NULL |
malloc() | Pointer to space | NULL |
mblen(), s != NULL | Number of bytes | −1 |
mbrlen(), s != NULL | Number of bytes or status | (size_t)(-1) |
mbrtoc16() | Number of bytes or status | (size_t)(-1) , errno == EILSEQ |
mbrtoc32() | Number of bytes or status | (size_t)(-1) , errno == EILSEQ |
mbrtowc(), s != NULL | Number of bytes or status | (size_t)(-1) , errno == EILSEQ |
mbsrtowcs() | Number of non-null elements | (size_t)(-1) , errno == EILSEQ |
mbsrtowcs_s() | 0 | Nonzero |
mbstowcs() | Number of non-null elements | (size_t)(-1) |
mbstowcs_s() | 0 | Nonzero |
mbtowc(), s != NULL | Number of bytes | −1 |
memchr() | Pointer to located character | NULL |
mktime() | Calendar time | (time_t)(-1) |
mtx_init() | thrd_success | thrd_error |
mtx_lock() | thrd_success | thrd_error |
mtx_timedlock() | thrd_success | thrd_timedout or thrd_error |
mtx_trylock() | thrd_success | thrd_busy or thrd_error |
mtx_unlock() | thrd_success | thrd_error |
printf_s() | Number of characters (nonnegative) | Negative |
putc() | Character written | EOF 2 |
putwc() | Wide character written | WEOF |
raise() | 0 | Nonzero |
realloc() | Pointer to space | NULL |
remove() | 0 | Nonzero |
rename() | 0 | Nonzero |
setlocale() | Pointer to string | NULL |
setvbuf() | 0 | Nonzero |
scanf() | Number of conversions (nonnegative) | EOF (negative) |
scanf_s() | Number of conversions (nonnegative) | EOF (negative) |
signal() | Pointer to previous function | SIG_ERR , errno > 0 |
snprintf() | Number of characters that would be written (nonnegative) | Negative |
snprintf_s() | Number of characters that would be written (nonnegative) | Negative |
sprintf() | Number of non-null characters written | Negative |
sprintf_s() | Number of non-null characters written | Negative |
sscanf() | Number of conversions (nonnegative) | EOF (negative) |
sscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
strchr() | Pointer to located character | NULL |
strerror_s() | 0 | Nonzero |
strftime() | Number of non-null characters | 0 |
strpbrk() | Pointer to located character | NULL |
strrchr() | Pointer to located character | NULL |
strstr() | Pointer to located string | NULL |
strtod() | Converted value | 0 , errno == ERANGE |
strtof() | Converted value | 0 , errno == ERANGE |
strtoimax() | Converted value | INTMAX_MAX or INTMAX_MIN , errno == ERANGE |
strtok() | Pointer to first character of a token | NULL |
strtok_s() | Pointer to first character of a token | NULL |
strtol() | Converted value | LONG_MAX or LONG_MIN , errno == ERANGE |
strtold() | Converted value | 0, errno == ERANGE |
strtoll() | Converted value | LLONG_MAX or LLONG_MIN , errno == ERANGE |
strtoumax() | Converted value | UINTMAX_MAX , errno == ERANGE |
strtoul() | Converted value | ULONG_MAX , errno == ERANGE |
strtoull() | Converted value | ULLONG_MAX , errno == ERANGE |
strxfrm() | Length of transformed string | >= n |
swprintf() | Number of non-null wide characters | Negative |
swprintf_s() | Number of non-null wide characters | Negative |
swscanf() | Number of conversions (nonnegative) | EOF (negative) |
swscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
thrd_create() | thrd_success | thrd_nomem or thrd_error |
thrd_detach() | thrd_success | thrd_error |
thrd_join() | thrd_success | thrd_error |
thrd_sleep() | 0 | Negative |
time() | Calendar time | (time_t)(-1) |
timespec_get() | Base | 0 |
tmpfile() | Pointer to stream | NULL |
tmpfile_s() | 0 | Nonzero |
tmpnam() | Non-null pointer | NULL |
tmpnam_s() | 0 | Nonzero |
tss_create() | thrd_success | thrd_error |
tss_get() | Value of thread-specific storage | 0 |
tss_set() | thrd_success | thrd_error |
ungetc() | Character pushed back | EOF (see below ) |
ungetwc() | Character pushed back | WEOF |
vfprintf() | Number of characters (nonnegative) | Negative |
vfprintf_s() | Number of characters (nonnegative) | Negative |
vfscanf() | Number of conversions (nonnegative) | EOF (negative) |
vfscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
vfwprintf() | Number of wide characters (nonnegative) | Negative |
vfwprintf_s() | Number of wide characters (nonnegative) | Negative |
vfwscanf() | Number of conversions (nonnegative) | EOF (negative) |
vfwscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
vprintf_s() | Number of characters (nonnegative) | Negative |
vscanf() | Number of conversions (nonnegative) | EOF (negative) |
vscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
vsnprintf() | Number of characters that would be written (nonnegative) | Negative |
vsnprintf_s() | Number of characters that would be written (nonnegative) | Negative |
vsprintf() | Number of non-null characters (nonnegative) | Negative |
vsprintf_s() | Number of non-null characters (nonnegative) | Negative |
vsscanf() | Number of conversions (nonnegative) | EOF (negative) |
vsscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
vswprintf() | Number of non-null wide characters | Negative |
vswprintf_s() | Number of non-null wide characters | Negative |
vswscanf() | Number of conversions (nonnegative) | EOF (negative) |
vswscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
vwprintf_s() | Number of wide characters (nonnegative) | Negative |
vwscanf() | Number of conversions (nonnegative) | EOF (negative) |
vwscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
wcrtomb() | Number of bytes stored | (size_t)(-1) |
wcschr() | Pointer to located wide character | NULL |
wcsftime() | Number of non-null wide characters | 0 |
wcspbrk() | Pointer to located wide character | NULL |
wcsrchr() | Pointer to located wide character | NULL |
wcsrtombs() | Number of non-null bytes | (size_t)(-1) , errno == EILSEQ |
wcsrtombs_s() | 0 | Nonzero |
wcsstr() | Pointer to located wide string | NULL |
wcstod() | Converted value | 0 , errno == ERANGE |
wcstof() | Converted value | 0 , errno == ERANGE |
wcstoimax() | Converted value | INTMAX_MAX or INTMAX_MIN , errno == ERANGE |
wcstok() | Pointer to first wide character of a token | NULL |
wcstok_s() | Pointer to first wide character of a token | NULL |
wcstol() | Converted value | LONG_MAX or LONG_MIN , errno == ERANGE |
wcstold() | Converted value | 0 , errno == ERANGE |
wcstoll() | Converted value | LLONG_MAX or LLONG_MIN , errno == ERANGE |
wcstombs() | Number of non-null bytes | (size_t)(-1) |
wcstombs_s() | 0 | Nonzero |
wcstoumax() | Converted value | UINTMAX_MAX , errno == ERANGE |
wcstoul() | Converted value | ULONG_MAX , errno == ERANGE |
wcstoull() | Converted value | ULLONG_MAX , errno == ERANGE |
wcsxfrm() | Length of transformed wide string | >= n |
wctob() | Converted character | EOF |
wctomb(), s != NULL | Number of bytes stored | −1 |
wctomb_s(), s != NULL | Number of bytes stored | −1 |
wctrans() | Valid argument to towctrans | 0 |
wctype() | Valid argument to iswctype | 0 |
wmemchr() | Pointer to located wide character | NULL |
wprintf_s() | Number of wide characters (nonnegative) | Negative |
wscanf() | Number of conversions (nonnegative) | EOF (negative) |
wscanf_s() | Number of conversions (nonnegative) | EOF (negative) |
+Note: According to [FIO35-C](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152389)[. Use feof() and ferror() to detect end-of-file and file errors when sizeof(int) == sizeof(char)](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152389), callers should verify end-of-file and file errors for the functions in this table as follows:
+
+
+1 By calling `ferror()` and `feof()`2 By calling `ferror()`
+
+The `ungetc()` function does not set the error indicator even when it fails, so it is not possible to check for errors reliably unless it is known that the argument is not equal to `EOF`. The C Standard \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\] states that "one character of pushback is guaranteed," so this should not be an issue if, at most, one character is ever pushed back before reading again. (See [FIO13-C](https://wiki.sei.cmu.edu/confluence/display/c/FIO13-C.+Never+push+back+anything+other+than+one+read+character)[. Never push back anything other than one read character](https://wiki.sei.cmu.edu/confluence/display/c/FIO13-C.+Never+push+back+anything+other+than+one+read+character).)
+
+## Noncompliant Code Example (setlocale())
+
+In this noncompliant code example, the function `utf8_to_wcs()` attempts to convert a sequence of UTF-8 characters to wide characters. It first invokes `setlocale()` to set the global locale to the implementation-defined `en_US.UTF-8` but does not check for failure. The `setlocale()` function will fail by returning a null pointer, for example, when the locale is not installed. The function may fail for other reasons as well, such as the lack of resources. Depending on the sequence of characters pointed to by `utf8`, the subsequent call to `mbstowcs()` may fail or result in the function storing an unexpected sequence of wide characters in the supplied buffer `wcs`.
+
+```cpp
+#include
+#include
+
+int utf8_to_wcs(wchar_t *wcs, size_t n, const char *utf8,
+ size_t *size) {
+ if (NULL == size) {
+ return -1;
+ }
+ setlocale(LC_CTYPE, "en_US.UTF-8");
+ *size = mbstowcs(wcs, utf8, n);
+ return 0;
+}
+
+```
+
+## Compliant Solution (setlocale())
+
+This compliant solution checks the value returned by `setlocale()` and avoids calling `mbstowcs()` if the function fails. The function also takes care to restore the locale to its initial setting before returning control to the caller.
+
+```cpp
+#include
+#include
+
+int utf8_to_wcs(wchar_t *wcs, size_t n, const char *utf8,
+ size_t *size) {
+ if (NULL == size) {
+ return -1;
+ }
+ const char *save = setlocale(LC_CTYPE, "en_US.UTF-8");
+ if (NULL == save) {
+ return -1;
+ }
+
+ *size = mbstowcs(wcs, utf8, n);
+ if (NULL == setlocale(LC_CTYPE, save)) {
+ return -1;
+ }
+ return 0;
+}
+
+```
+
+## Noncompliant Code Example (calloc())
+
+In this noncompliant code example, `temp_num`,` tmp2`, and `num_of_records` are derived from a [tainted source](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-taintedsource). Consequently, an attacker can easily cause `calloc()` to fail by providing a large value for `num_of_records`.
+
+```cpp
+#include
+#include
+
+enum { SIG_DESC_SIZE = 32 };
+
+typedef struct {
+ char sig_desc[SIG_DESC_SIZE];
+} signal_info;
+
+void func(size_t num_of_records, size_t temp_num,
+ const char *tmp2, size_t tmp2_size_bytes) {
+ signal_info *start = (signal_info *)calloc(num_of_records,
+ sizeof(signal_info));
+
+ if (tmp2 == NULL) {
+ /* Handle error */
+ } else if (temp_num > num_of_records) {
+ /* Handle error */
+ } else if (tmp2_size_bytes < SIG_DESC_SIZE) {
+ /* Handle error */
+ }
+
+ signal_info *point = start + temp_num - 1;
+ memcpy(point->sig_desc, tmp2, SIG_DESC_SIZE);
+ point->sig_desc[SIG_DESC_SIZE - 1] = '\0';
+ /* ... */
+ free(start);
+}
+```
+When `calloc()` fails, it returns a null pointer that is assigned to `start`. If `start` is null, an attacker can provide a value for `temp_num` that, when scaled by `sizeof(signal_info)`, references a writable address to which control is eventually transferred. The contents of the string referenced by `tmp2` can then be used to overwrite the address, resulting in an arbitrary code execution [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability).
+
+## Compliant Solution (calloc())
+
+To correct this error, ensure the pointer returned by `calloc()` is not null:
+
+```cpp
+#include
+#include
+
+enum { SIG_DESC_SIZE = 32 };
+
+typedef struct {
+ char sig_desc[SIG_DESC_SIZE];
+} signal_info;
+
+void func(size_t num_of_records, size_t temp_num,
+ const char *tmp2, size_t tmp2_size_bytes) {
+ signal_info *start = (signal_info *)calloc(num_of_records,
+ sizeof(signal_info));
+ if (start == NULL) {
+ /* Handle allocation error */
+ } else if (tmp2 == NULL) {
+ /* Handle error */
+ } else if (temp_num > num_of_records) {
+ /* Handle error */
+ } else if (tmp2_size_bytes < SIG_DESC_SIZE) {
+ /* Handle error */
+ }
+
+ signal_info *point = start + temp_num - 1;
+ memcpy(point->sig_desc, tmp2, SIG_DESC_SIZE);
+ point->sig_desc[SIG_DESC_SIZE - 1] = '\0';
+ /* ... */
+ free(start);
+}
+```
+
+## Noncompliant Code Example (realloc())
+
+This noncompliant code example calls `realloc()` to resize the memory referred to by `p`. However, if `realloc()` fails, it returns a null pointer and the connection between the original block of memory and `p` is lost, resulting in a memory leak.
+
+```cpp
+#include
+
+void *p;
+void func(size_t new_size) {
+ if (new_size == 0) {
+ /* Handle error */
+ }
+ p = realloc(p, new_size);
+ if (p == NULL) {
+ /* Handle error */
+ }
+}
+```
+This code example complies with [MEM04-C](https://wiki.sei.cmu.edu/confluence/display/c/MEM04-C.+Beware+of+zero-length+allocations)[. Do not perform zero-length allocations](https://wiki.sei.cmu.edu/confluence/display/c/MEM04-C.+Beware+of+zero-length+allocations).
+
+## Compliant Solution (realloc())
+
+In this compliant solution, the result of `realloc()` is assigned to the temporary pointer `q` and validated before it is assigned to the original pointer `p`:
+
+```cpp
+#include
+
+void *p;
+void func(size_t new_size) {
+ void *q;
+
+ if (new_size == 0) {
+ /* Handle error */
+ }
+
+ q = realloc(p, new_size);
+ if (q == NULL) {
+ /* Handle error */
+ } else {
+ p = q;
+ }
+}
+```
+
+## Noncompliant Code Example (fseek())
+
+In this noncompliant code example, the `fseek()` function is used to set the file position to a location `offset` in the file referred to by `file` prior to reading a sequence of bytes from the file. However, if an I/O error occurs during the seek operation, the subsequent read will fill the buffer with the wrong contents.
+
+```cpp
+#include
+
+size_t read_at(FILE *file, long offset,
+ void *buf, size_t nbytes) {
+ fseek(file, offset, SEEK_SET);
+ return fread(buf, 1, nbytes, file);
+}
+
+```
+
+## Compliant Solution (fseek())
+
+According to the C Standard, the `fseek()` function returns a nonzero value to indicate that an error occurred. This compliant solution tests for this condition before reading from a file to eliminate the chance of operating on the wrong portion of the file if `fseek()` fails:
+
+```cpp
+#include
+
+size_t read_at(FILE *file, long offset,
+ void *buf, size_t nbytes) {
+ if (fseek(file, offset, SEEK_SET) != 0) {
+ /* Indicate error to caller */
+ return 0;
+ }
+ return fread(buf, 1, nbytes, file);
+}
+
+```
+
+## Noncompliant Code Example (snprintf())
+
+In this noncompliant code example, `snprintf()` is assumed to succeed. However, if the call fails (for example, because of insufficient memory, as described in GNU libc bug [441945](http://bugzilla.redhat.com/show_bug.cgi?id=441945)), the subsequent call to `log_message()` has [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) because the character buffer is uninitialized and need not be null-terminated.
+
+```cpp
+#include
+
+extern void log_message(const char *);
+
+void f(int i, int width, int prec) {
+ char buf[40];
+ snprintf(buf, sizeof(buf), "i = %*.*i", width, prec, i);
+ log_message(buf);
+ /* ... */
+}
+
+```
+
+## Compliant Solution (snprintf())
+
+This compliant solution does not assume that `snprintf()` will succeed regardless of its arguments. It tests the return value of `snprintf()` before subsequently using the formatted buffer. This compliant solution also treats the case where the static buffer is not large enough for `snprintf()` to append the terminating null character as an error.
+
+```cpp
+#include
+#include
+
+extern void log_message(const char *);
+
+void f(int i, int width, int prec) {
+ char buf[40];
+ int n;
+ n = snprintf(buf, sizeof(buf), "i = %*.*i", width, prec, i);
+ if (n < 0 || n >= sizeof(buf)) {
+ /* Handle snprintf() error */
+ strcpy(buf, "unknown error");
+ }
+ log_message(buf);
+}
+
+```
+
+## Compliant Solution (snprintf(null))
+
+If unknown, the length of the formatted string can be discovered by invoking `snprintf()` with a null buffer pointer to determine the size required for the output, then dynamically allocating a buffer of sufficient size, and finally calling `snprintf()` again to format the output into the dynamically allocated buffer. Even with this approach, the success of all calls still needs to be tested, and any errors must be appropriately handled. A possible optimization is to first attempt to format the string into a reasonably small buffer allocated on the stack and, only when the buffer turns out to be too small, dynamically allocate one of a sufficient size:
+
+```cpp
+#include
+#include
+#include
+
+extern void log_message(const char *);
+
+void f(int i, int width, int prec) {
+ char buffer[20];
+ char *buf = buffer;
+ int n = sizeof(buffer);
+ const char fmt[] = "i = %*.*i";
+
+ n = snprintf(buf, n, fmt, width, prec, i);
+ if (n < 0) {
+ /* Handle snprintf() error */
+ strcpy(buffer, "unknown error");
+ goto write_log;
+ }
+
+ if (n < sizeof(buffer)) {
+ goto write_log;
+ }
+
+ buf = (char *)malloc(n + 1);
+ if (NULL == buf) {
+ /* Handle malloc() error */
+ strcpy(buffer, "unknown error");
+ goto write_log;
+ }
+
+ n = snprintf(buf, n, fmt, width, prec, i);
+ if (n < 0) {
+ /* Handle snprintf() error */
+ strcpy(buffer, "unknown error");
+ }
+
+write_log:
+ log_message(buf);
+
+ if (buf != buffer) {
+ free(buf);
+ }
+}
+
+```
+This solution uses the `goto` statement, as suggested in [MEM12-C](https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consider+using+a+goto+chain+when+leaving+a+function+on+error+when+using+and+releasing+resources)[. Consider using a goto chain when leaving a function on error when using and releasing resources](https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consider+using+a+goto+chain+when+leaving+a+function+on+error+when+using+and+releasing+resources).
+
+## Exceptions
+
+**ERR33-C-EX1:** It is acceptable to ignore the return value of a function if:
+
+* that function cannot fail.
+* its return value is inconsequential; that is, it does not indicate an error.
+* it is one of a handful of functions whose return values are not traditionally checked. These functions are listed in the following table:
+**Functions for which Return Values Need Not Be Checked**
+
+ Function | Successful Return | Error Return |
putchar() | Character written | EOF |
putwchar() | Wide character written | WEOF |
puts() | Nonnegative | EOF (negative) |
printf() , vprintf() | Number of characters (nonnegative) | Negative |
wprintf() , vwprintf() | Number of wide characters (nonnegative) | Negative |
kill_dependency() | The input parameter | NA |
memcpy() , wmemcpy() | The destination input parameter | NA |
memmove() , wmemmove() | The destination input parameter | NA |
strcpy() , wcscpy() | The destination input parameter | NA |
strncpy() , wcsncpy() | The destination input parameter | NA |
strcat() , wcscat() | The destination input parameter | NA |
strncat() , wcsncat() | The destination input parameter | NA |
memset() , wmemset() | The destination input parameter | NA |
+The function's results should be explicitly cast to `void` to signify programmer intent:
+
+
+```cpp
+int main() {
+ (void) printf("Hello, world\n"); // printf() return value safely ignored
+}
+
+```
+
+## Risk Assessment
+
+Failing to detect error conditions can lead to unpredictable results, including [abnormal program termination](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-abnormaltermination) and [denial-of-service attacks](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-denial-of-service) or, in some situations, could even allow an attacker to run arbitrary code.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
ERR33-C | High | Likely | Medium | P18 | L1 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | error-information-unusederror-information-unused-computed | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-ERR33 | |
CodeSonar | 7.1p0 | LANG.FUNCS.IRV | Ignored return value |
Compass/ROSE | | | Can detect violations of this recommendation when checking for violations of EXP12-C. Do not ignore values returned by functions and EXP34-C . Do not dereference null pointers |
Coverity | 2017.07 | MISRA C 2012 Rule 22.8 MISRA C 2012 Rule 22.9 MISRA C 2012 Rule 22.10 | Implemented |
Helix QAC | 2022.3 | C3200 C++2820, C++2821, C++2822, C++2823, C++2824, C++2930, C++2931, C++2932, C++2933, C++2934, C++3802, C++3803, C++3804 | |
Klocwork | 2022.3 | NPD.CHECK.MUST NPD.FUNC.MUST SV.RVT.RETVAL_NOTTESTED | |
LDRA tool suite | 9.7.1 | 80 D | Partially implemented |
Parasoft C/C++test | 2022.1 | CERT_C-ERR33-a CERT_C-ERR33-b CERT_C-ERR33-c CERT_C-ERR33-d | The value returned by a function having non-void return type shall be used The value returned by a function having non-void return type shall be used Avoid null pointer dereferencing Always check the returned value of non-void function |
Parasoft Insure++ | | | Runtime analysis |
PC-lint Plus | 1.4 | 534 | Partially supported |
Polyspace Bug Finder | R2022b | CERT C: Rule ERR33-C | Checks for: Errno not checkedrrno not checked, return value of a sensitive function not checkedeturn value of a sensitive function not checked, unprotected dynamic memory allocationnprotected dynamic memory allocation. Rule partially covered. |
PRQA QA-C | 9.7 | 3200 | Partially implemented |
PRQA QA-C++ | 4.4 | 2820, 2821, 2822, 2823, 2824, 2930, 2931, 2932, 2933, 2934, 3802, 3803, 3804 | |
RuleChecker | 22.04 | error-information-unused | Partially checked |
TrustInSoft Analyzer | 1.38 | pointer arithmetic | Exhaustively verified. |
+
+
+## Related Vulnerabilities
+
+The [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) in Adobe Flash \[[VU\#159523](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-VU%23159523)\] arises because Flash neglects to check the return value from `calloc()`. Even when `calloc()` returns a null pointer, Flash writes to an offset from the return value. Dereferencing a null pointer usually results in a program crash, but dereferencing an offset from a null pointer allows an [exploit](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-exploit) to succeed without crashing the program.
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ERR33-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-252/CWE-253/CWE-391 and ERR33-C/POS34-C**
+
+Independent( ERR33-C, POS54-C, FLP32-C, ERR34-C) Intersection( CWE-252, CWE-253) = Ø CWE-391 = Union( CWE-252, CWE-253) CWE-391 = Union( ERR33-C, POS34-C, list) where list =
+
+* Ignoring return values of functions outside the C or POSIX standard libraries
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+The rule is enforced in the context of a single function.
+
+## References
+
+* CERT-C: [ERR33-C: Detect and handle standard library errors](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/ERR33-C/DetectAndHandleStandardLibraryErrors.ql b/c/cert/src/rules/ERR33-C/DetectAndHandleStandardLibraryErrors.ql
new file mode 100644
index 0000000000..5e473b226e
--- /dev/null
+++ b/c/cert/src/rules/ERR33-C/DetectAndHandleStandardLibraryErrors.ql
@@ -0,0 +1,475 @@
+/**
+ * @id c/cert/detect-and-handle-standard-library-errors
+ * @name ERR33-C: Detect and handle standard library errors
+ * @description Detect and handle standard library errors. Undetected failures can lead to
+ * unexpected or undefined behavior.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/err33-c
+ * correctness
+ * external/cert/severity/high
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p18
+ * external/cert/level/l1
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import semmle.code.cpp.commons.NULL
+import codingstandards.cpp.ReadErrorsAndEOF
+import semmle.code.cpp.dataflow.DataFlow
+
+ComparisonOperation getAValidComparison(string spec) {
+ spec = "=0" and result.(EqualityOperation).getAnOperand().getValue() = "0"
+ or
+ spec = "=NULL" and result.(EqualityOperation).getAnOperand() instanceof NULL
+ or
+ spec = "=EOF" and result.(EqualityOperation).getAnOperand() = any(EOFInvocation i).getExpr()
+ or
+ spec = "=WEOF" and result.(EqualityOperation).getAnOperand() = any(WEOFInvocation i).getExpr()
+ or
+ spec = "=thrd_error" and
+ result.(EqualityOperation).getAnOperand().(EnumConstantAccess).toString() = "thrd_error"
+ or
+ spec = "=thrd_nomem" and
+ result.(EqualityOperation).getAnOperand().(EnumConstantAccess).toString() = "thrd_nomem"
+ or
+ spec = "=thrd_timedout" and
+ result.(EqualityOperation).getAnOperand().(EnumConstantAccess).toString() = "thrd_timedout"
+ or
+ spec = "=thrd_busy" and
+ result.(EqualityOperation).getAnOperand().(EnumConstantAccess).toString() = "thrd_busy"
+ or
+ spec = "=UINTMAX_MAX" and
+ result.(EqualityOperation).getAnOperand() =
+ any(MacroInvocation i | i.getMacroName() = "UINTMAX_MAX").getExpr()
+ or
+ spec = "=ULONG_MAX" and
+ result.(EqualityOperation).getAnOperand() =
+ any(MacroInvocation i | i.getMacroName() = "ULONG_MAX").getExpr()
+ or
+ spec = "=ULLONG_MAX" and
+ result.(EqualityOperation).getAnOperand() =
+ any(MacroInvocation i | i.getMacroName() = "ULLONG_MAX").getExpr()
+ or
+ spec = "=SIG_ERR" and
+ result.(EqualityOperation).getAnOperand() =
+ any(MacroInvocation i | i.getMacroName() = "SIG_ERR").getExpr()
+ or
+ spec = "=INTMAX_MAX" and
+ result.(EqualityOperation).getAnOperand() =
+ any(MacroInvocation i | i.getMacroName() = ["INTMAX_MAX", "INTMAX_MIN"]).getExpr()
+ or
+ spec = "=LONG_MAX" and
+ result.(EqualityOperation).getAnOperand() =
+ any(MacroInvocation i | i.getMacroName() = ["LONG_MAX", "LONG_MIN"]).getExpr()
+ or
+ spec = "=LLONG_MAX" and
+ result.(EqualityOperation).getAnOperand() =
+ any(MacroInvocation i | i.getMacroName() = ["LLONG_MAX", "LLONG_MIN"]).getExpr()
+ or
+ spec = "=-1" and
+ result.(EqualityOperation).getAnOperand().(UnaryMinusExpr).getOperand().getValue() = "1"
+ or
+ spec = "=int" and
+ result.(EqualityOperation).getAnOperand().getType() instanceof IntType
+ or
+ spec = "<0" and
+ result.(RelationalOperation).getOperator() = ["<", ">="] and
+ result.(RelationalOperation).getGreaterOperand().getValue() = "0"
+ or
+ spec = "="] and
+ result.(RelationalOperation).getLesserOperand().getType() instanceof IntType
+}
+
+/**
+ * Calls whose return value must be checked
+ * using an `errOperator` against `errValue`
+ */
+abstract class ExpectedErrReturn extends FunctionCall {
+ ComparisonOperation errOperator;
+
+ ComparisonOperation getErrOperator() { result = errOperator }
+}
+
+/**
+ * Calls that must be checked agains `0`.
+ *
+ * example:
+ * ```
+ * if (strftime(b, 10, "", local) == 0) { ... }
+ * ```
+ */
+class ExpectedErrReturnEqZero extends ExpectedErrReturn {
+ ExpectedErrReturnEqZero() {
+ this.getTarget()
+ .hasName([
+ "asctime_s", "at_quick_exit", "atexit", "ctime_s", "fgetpos", "fopen_s", "freopen_s",
+ "fseek", "fsetpos", "mbsrtowcs_s", "mbstowcs_s", "raise", "remove", "rename", "setvbuf",
+ "strerror_s", "strftime", "strtod", "strtof", "strtold", "timespec_get", "tmpfile_s",
+ "tmpnam_s", "tss_get", "wcsftime", "wcsrtombs_s", "wcstod", "wcstof", "wcstold",
+ "wcstombs_s", "wctrans", "wctype"
+ ])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=0") }
+}
+
+/**
+ * Calls that must be checked agains `NULL`.
+ *
+ * example:
+ * ```
+ * if (aligned_alloc(0, 0) == NULL) { ... }
+ * ```
+ */
+class ExpectedErrReturnEqNull extends ExpectedErrReturn {
+ ExpectedErrReturnEqNull() {
+ this.getTarget()
+ .hasName([
+ "aligned_alloc", "bsearch_s", "bsearch", "calloc", "fgets", "fopen", "freopen",
+ "getenv_s", "getenv", "gets_s", "gmtime_s", "gmtime", "localtime_s", "localtime",
+ "malloc", "memchr", "realloc", "setlocale", "strchr", "strpbrk", "strrchr", "strstr",
+ "strtok_s", "strtok", "tmpfile", "tmpnam", "wcschr", "wcspbrk", "wcsrchr", "wcsstr",
+ "wcstok_s", "wcstok", "wmemchr"
+ ])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=0") }
+}
+
+/**
+ * Calls that must be checked agains `EOF` or `WEOF`.
+ *
+ * example:
+ * ```
+ * if (wctob(0) == EOF) { ... }
+ * ```
+ */
+class ExpectedErrReturnEqEof extends ExpectedErrReturn {
+ ExpectedErrReturnEqEof() {
+ this.getTarget()
+ .hasName([
+ "fclose", "fflush", "fputs", "fputws", "fscanf_s", "fscanf", "fwscanf_s", "fwscanf",
+ "scanf_s", "scanf", "sscanf_s", "sscanf", "swscanf_s", "swscanf", "ungetc", "vfscanf_s",
+ "vfscanf", "vfwscanf_s", "vfwscanf", "vscanf_s", "vscanf", "vsscanf_s", "vsscanf",
+ "vswscanf_s", "vswscanf", "vwscanf_s", "vwscanf", "wctob", "wscanf_s", "wscanf",
+ "fgetc", "fputc", "getc", "getchar", "putc", "putchar", "puts"
+ ])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=EOF") }
+}
+
+/**
+ * Calls that must be checked agains`WEOF`.
+ *
+ * example:
+ * ```
+ * if (btowc(0) == WEOF) { ... }
+ * ```
+ */
+class ExpectedErrReturnEqWeof extends ExpectedErrReturn {
+ ExpectedErrReturnEqWeof() {
+ this.getTarget()
+ .hasName(["btowc", "fgetwc", "fputwc", "getwc", "getwchar", "putwc", "ungetwc", "putwchar"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=WEOF") }
+}
+
+/**
+ * Calls that must be checked agains an enun constant.
+ *
+ * example:
+ * ```
+ * if (cnd_broadcast(&q) == thrd_error) { ... }
+ * ```
+ */
+class ExpectedErrReturnEqEnumConstant_thrd_error extends ExpectedErrReturn {
+ ExpectedErrReturnEqEnumConstant_thrd_error() {
+ this.getTarget()
+ .hasName([
+ "cnd_broadcast", "cnd_init", "cnd_signal", "cnd_timedwait", "cnd_wait", "mtx_init",
+ "mtx_lock", "mtx_timedlock", "mtx_trylock", "mtx_unlock", "thrd_create", "thrd_detach",
+ "thrd_join", "tss_create", "tss_set"
+ ])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=thrd_error") }
+}
+
+class ExpectedErrReturnEqEnumConstant_thrd_nomem extends ExpectedErrReturn {
+ ExpectedErrReturnEqEnumConstant_thrd_nomem() {
+ this.getTarget().hasName(["cnd_init", "thrd_create"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=thrd_nomem") }
+}
+
+class ExpectedErrReturnEqEnumConstant_thrd_timedout extends ExpectedErrReturn {
+ ExpectedErrReturnEqEnumConstant_thrd_timedout() {
+ this.getTarget().hasName(["cnd_timedwait", "mtx_timedlock"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=thrd_timedout") }
+}
+
+class ExpectedErrReturnEqEnumConstant_thrd_busy extends ExpectedErrReturn {
+ ExpectedErrReturnEqEnumConstant_thrd_busy() { this.getTarget().hasName(["mtx_trylock"]) }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=thrd_busy") }
+}
+
+/**
+ * Calls that must be checked agains a macro.
+ *
+ * example:
+ * ```
+ * if (strtoumax(str, &endptr, 0) == UINTMAX_MAX) { ... }
+ * ```
+ */
+class ExpectedErrReturnEqMacroInvocation_UINTMAX_MAX extends ExpectedErrReturn {
+ ExpectedErrReturnEqMacroInvocation_UINTMAX_MAX() {
+ this.getTarget().hasName(["strtoumax", "wcstoumax"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=UINTMAX_MAX") }
+}
+
+class ExpectedErrReturnEqMacroInvocation_ULONG_MAX extends ExpectedErrReturn {
+ ExpectedErrReturnEqMacroInvocation_ULONG_MAX() {
+ this.getTarget().hasName(["strtoul", "wcstoul"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=ULONG_MAX") }
+}
+
+class ExpectedErrReturnEqMacroInvocation_ULLONG_MAX extends ExpectedErrReturn {
+ ExpectedErrReturnEqMacroInvocation_ULLONG_MAX() {
+ this.getTarget().hasName(["strtoull", "wcstoull"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=ULLONG_MAX") }
+}
+
+class ExpectedErrReturnEqMacroInvocation_SIG_ERR extends ExpectedErrReturn {
+ ExpectedErrReturnEqMacroInvocation_SIG_ERR() { this.getTarget().hasName(["signal"]) }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=SIG_ERR") }
+}
+
+class ExpectedErrReturnEqMacroInvocation_INTMAX_MAX extends ExpectedErrReturn {
+ ExpectedErrReturnEqMacroInvocation_INTMAX_MAX() {
+ this.getTarget().hasName(["strtoimax", "wcstoimax"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=INTMAX_MAX") }
+}
+
+class ExpectedErrReturnEqMacroInvocation_LONG_MAX extends ExpectedErrReturn {
+ ExpectedErrReturnEqMacroInvocation_LONG_MAX() { this.getTarget().hasName(["strtol", "wcstol"]) }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=LONG_MAX") }
+}
+
+class ExpectedErrReturnEqMacroInvocation_LLONG_MAX extends ExpectedErrReturn {
+ ExpectedErrReturnEqMacroInvocation_LLONG_MAX() {
+ this.getTarget().hasName(["strtoll", "wcstoll"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=LLONG_MAX") }
+}
+
+/**
+ * Calls that must be checked agains `-1`.
+ *
+ * example:
+ * ```
+ * if (clock() == (clock_t)(-1)) { ... }
+ * ```
+ */
+class ExpectedErrReturnEqMinusOne extends ExpectedErrReturn {
+ ExpectedErrReturnEqMinusOne() {
+ this.getTarget()
+ .hasName([
+ "c16rtomb", "c32rtomb", "clock", "ftell", "mbrtoc16", "mbrtoc32", "mbsrtowcs",
+ "mbstowcs", "mktime", "time", "wcrtomb", "wcsrtombs", "wcstombs"
+ ])
+ or
+ // functions that behave differently when the first argument is NULL
+ not this.getArgument(0) instanceof NULL and
+ this.getTarget().hasName(["mblen", "mbrlen", "mbrtowc", "mbtowc", "wctomb_s", "wctomb"])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=-1") }
+}
+
+/**
+ * Calls that must be checked agains an integer value.
+ *
+ * example:
+ * ```
+ * if (fread(b, sizeof *b, SIZE, fp) == SIZE) { ... }
+ * ```
+ */
+class ExpectedErrReturnEqInt extends ExpectedErrReturn {
+ ExpectedErrReturnEqInt() { this.getTarget().hasName(["fread", "fwrite"]) }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("=int") }
+}
+
+/**
+ * Calls that must be compared to `0`.
+ *
+ * example:
+ * ```
+ * if (snprintf(NULL, 0, fmt, sqrt(2) >= 0) { ... }
+ * ```
+ */
+class ExpectedErrReturnLtZero extends ExpectedErrReturn {
+ ExpectedErrReturnLtZero() {
+ this.getTarget()
+ .hasName([
+ "fprintf_s", "fprintf", "fwprintf_s", "fwprintf", "printf_s", "snprintf_s", "snprintf",
+ "sprintf_s", "sprintf", "swprintf_s", "swprintf", "thrd_sleep", "vfprintf_s",
+ "vfprintf", "vfwprintf_s", "vfwprintf", "vprintf_s", "vsnprintf_s", "vsnprintf",
+ "vsprintf_s", "vsprintf", "vswprintf_s", "vswprintf", "vwprintf_s", "wprintf_s",
+ "printf", "vprintf", "wprintf", "vwprintf"
+ ])
+ }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison("<0") }
+}
+
+/**
+ * Calls that must be compared to an integer value.
+ *
+ * example:
+ * ```
+ * if (strxfrm(out, in, sizeof out) >= 10) { ... }
+ * ```
+ */
+class ExpectedErrReturnLtInt extends ExpectedErrReturn {
+ ExpectedErrReturnLtInt() { this.getTarget().hasName(["strxfrm", "wcsxfrm"]) }
+
+ override ComparisonOperation getErrOperator() { result = getAValidComparison(" Do not compare function pointers to constant values
+
+
+## Description
+
+Comparing a function pointer to a value that is not a null function pointer of the same type will be diagnosed because it typically indicates programmer error and can result in [unexpected behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-unexpectedbehavior). Implicit comparisons will be diagnosed, as well.
+
+## Noncompliant Code Example
+
+In this noncompliant code example, the addresses of the POSIX functions `getuid` and `geteuid` are compared for equality to 0. Because no function address shall be null, the first subexpression will always evaluate to false (0), and the second subexpression always to true (nonzero). Consequently, the entire expression will always evaluate to true, leading to a potential security vulnerability.
+
+```cpp
+/* First the options that are allowed only for root */
+if (getuid == 0 || geteuid != 0) {
+ /* ... */
+}
+
+```
+
+## Noncompliant Code Example
+
+In this noncompliant code example, the function pointers `getuid` and `geteuid` are compared to 0. This example is from an actual [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) ([VU\#837857](http://www.kb.cert.org/vuls/id/837857)) discovered in some versions of the X Window System server. The vulnerability exists because the programmer neglected to provide the open and close parentheses following the `geteuid()` function identifier. As a result, the `geteuid` token returns the address of the function, which is never equal to 0. Consequently, the `or` condition of this `if` statement is always true, and access is provided to the protected block for all users. Many compilers issue a warning noting such pointless expressions. Therefore, this coding error is normally detected by adherence to [MSC00-C. Compile cleanly at high warning levels](https://wiki.sei.cmu.edu/confluence/display/c/MSC00-C.+Compile+cleanly+at+high+warning+levels).
+
+```cpp
+/* First the options that are allowed only for root */
+if (getuid() == 0 || geteuid != 0) {
+ /* ... */
+}
+
+```
+
+## Compliant Solution
+
+The solution is to provide the open and close parentheses following the `geteuid` token so that the function is properly invoked:
+
+```cpp
+/* First the options that are allowed only for root */
+if (getuid() == 0 || geteuid() != 0) {
+ /* ... */
+}
+
+```
+
+## Compliant Solution
+
+A function pointer can be compared to a null function pointer of the same type:
+
+```cpp
+/* First the options that are allowed only for root */
+if (getuid == (uid_t(*)(void))0 || geteuid != (uid_t(*)(void))0) {
+ /* ... */
+}
+
+```
+This code should not be diagnosed by an analyzer.
+
+## Noncompliant Code Example
+
+In this noncompliant code example, the function pointer `do_xyz` is implicitly compared unequal to 0:
+
+```cpp
+int do_xyz(void);
+
+int f(void) {
+/* ... */
+ if (do_xyz) {
+ return -1; /* Indicate failure */
+ }
+/* ... */
+ return 0;
+}
+
+```
+
+## Compliant Solution
+
+In this compliant solution, the function `do_xyz()` is invoked and the return value is compared to 0:
+
+```cpp
+int do_xyz(void);
+
+int f(void) {
+/* ... */
+ if (do_xyz()) {
+ return -1; /* Indicate failure */
+ }
+/* ... */
+ return 0;
+}
+
+```
+
+## Risk Assessment
+
+Errors of omission can result in unintended program flow.
+
+ Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP16-C | Low | Likely | Medium | P6 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 24.04 | function-name-constant-comparison | Partially checked |
Coverity | 2017.07 | BAD_COMPARE | Can detect the specific instance where the address of a function is compared against 0, such as in the case of geteuid versus getuid() in the implementation-specific details |
GCC | 4.3.5 | | Can detect violations of this recommendation when the -Wall flag is used |
Helix QAC | 2024.4 | C0428, C3004, C3344 | |
Klocwork | 2024.4 | CWARN.NULLCHECK.FUNCNAMECWARN.FUNCADDR | |
LDRA tool suite | 9.7.1 | 99 S | Partially implemented |
Parasoft C/C++test | 2024.2 | CERT_C-EXP16-a | Function address should not be compared to zero |
PC-lint Plus | 1.4 | 2440, 2441 | Partially supported: reports address of function, array, or variable directly or indirectly compared to null |
PVS-Studio | 7.35 | V516, V1058 | |
RuleChecker | 24.04 | function-name-constant-comparison | Partially checked |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP16-C).
+
+## Related Guidelines
+
+
+
+
+## Bibliography
+
+ \[ Hatton 1995 \] | Section 2.7.2, "Errors of Omission and Addition" |
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [EXP16-C: Do not compare function pointers to constant values](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP16-C/DoNotCompareFunctionPointersToConstantValues.ql b/c/cert/src/rules/EXP16-C/DoNotCompareFunctionPointersToConstantValues.ql
new file mode 100644
index 0000000000..5f347d817a
--- /dev/null
+++ b/c/cert/src/rules/EXP16-C/DoNotCompareFunctionPointersToConstantValues.ql
@@ -0,0 +1,73 @@
+/**
+ * @id c/cert/do-not-compare-function-pointers-to-constant-values
+ * @name EXP16-C: Do not compare function pointers to constant values
+ * @description Comparing function pointers to a constant value is not reliable and likely indicates
+ * a programmer error.
+ * @kind problem
+ * @precision very-high
+ * @problem.severity error
+ * @tags external/cert/id/exp16-c
+ * correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p6
+ * external/cert/level/l2
+ * external/cert/obligation/recommendation
+ */
+
+import cpp
+import semmle.code.cpp.controlflow.IRGuards
+import codingstandards.c.cert
+import codingstandards.cpp.types.FunctionType
+import codingstandards.cpp.exprs.FunctionExprs
+import codingstandards.cpp.exprs.Guards
+
+final class FinalElement = Element;
+
+abstract class EffectivelyComparison extends FinalElement {
+ abstract string getExplanation();
+
+ abstract FunctionExpr getFunctionExpr();
+}
+
+final class FinalComparisonOperation = ComparisonOperation;
+
+class ExplicitComparison extends EffectivelyComparison, FinalComparisonOperation {
+ Expr constantExpr;
+ FunctionExpr funcExpr;
+
+ ExplicitComparison() {
+ funcExpr = getAnOperand() and
+ constantExpr = getAnOperand() and
+ exists(constantExpr.getValue()) and
+ not funcExpr = constantExpr and
+ not constantExpr.getExplicitlyConverted().getUnderlyingType() =
+ funcExpr.getExplicitlyConverted().getUnderlyingType()
+ }
+
+ override string getExplanation() { result = "$@ compared to constant value." }
+
+ override FunctionExpr getFunctionExpr() { result = funcExpr }
+}
+
+class ImplicitComparison extends EffectivelyComparison, GuardCondition {
+ ImplicitComparison() {
+ this instanceof FunctionExpr and
+ not getParent() instanceof ComparisonOperation
+ }
+
+ override string getExplanation() { result = "$@ undergoes implicit constant comparison." }
+
+ override FunctionExpr getFunctionExpr() { result = this }
+}
+
+from EffectivelyComparison comparison, FunctionExpr funcExpr, Element function, string funcName
+where
+ not isExcluded(comparison,
+ Expressions2Package::doNotCompareFunctionPointersToConstantValuesQuery()) and
+ funcExpr = comparison.getFunctionExpr() and
+ not exists(NullFunctionCallGuard nullGuard | nullGuard.getFunctionExpr() = funcExpr) and
+ function = funcExpr.getFunction() and
+ funcName = funcExpr.describe()
+select comparison, comparison.getExplanation(), function, funcName
diff --git a/c/cert/src/rules/EXP30-C/DependenceOnOrderOfFunctionArgumentsForSideEffects.ql b/c/cert/src/rules/EXP30-C/DependenceOnOrderOfFunctionArgumentsForSideEffects.ql
index 3070f8d310..48b9487728 100644
--- a/c/cert/src/rules/EXP30-C/DependenceOnOrderOfFunctionArgumentsForSideEffects.ql
+++ b/c/cert/src/rules/EXP30-C/DependenceOnOrderOfFunctionArgumentsForSideEffects.ql
@@ -8,15 +8,19 @@
* @problem.severity warning
* @tags external/cert/id/exp30-c
* correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
import codingstandards.cpp.SideEffect
-import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.dataflow.TaintTracking
-import semmle.code.cpp.valuenumbering.GlobalValueNumberingImpl
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
/** Holds if the function's return value is derived from the `AliasParamter` p. */
predicate returnValueDependsOnAliasParameter(AliasParameter p) {
diff --git a/c/cert/src/rules/EXP30-C/DependenceOnOrderOfScalarEvaluationForSideEffects.ql b/c/cert/src/rules/EXP30-C/DependenceOnOrderOfScalarEvaluationForSideEffects.ql
index c478a3d51e..51b505ec63 100644
--- a/c/cert/src/rules/EXP30-C/DependenceOnOrderOfScalarEvaluationForSideEffects.ql
+++ b/c/cert/src/rules/EXP30-C/DependenceOnOrderOfScalarEvaluationForSideEffects.ql
@@ -8,6 +8,11 @@
* @problem.severity warning
* @tags external/cert/id/exp30-c
* correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/EXP32-C/DoNotAccessVolatileObjectWithNonVolatileReference.md b/c/cert/src/rules/EXP32-C/DoNotAccessVolatileObjectWithNonVolatileReference.md
new file mode 100644
index 0000000000..aa2dc7036a
--- /dev/null
+++ b/c/cert/src/rules/EXP32-C/DoNotAccessVolatileObjectWithNonVolatileReference.md
@@ -0,0 +1,104 @@
+# EXP32-C: Do not access a volatile object through a nonvolatile reference
+
+This query implements the CERT-C rule EXP32-C:
+
+> Do not access a volatile object through a nonvolatile reference
+
+
+## Description
+
+An object that has volatile-qualified type may be modified in ways unknown to the [implementation](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) or have other unknown [side effects](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-sideeffect). Referencing a volatile object by using a non-volatile lvalue is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). The C Standard, 6.7.3 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], states
+
+> If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.
+
+
+See [undefined behavior 65](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_65).
+
+## Noncompliant Code Example
+
+In this noncompliant code example, a volatile object is accessed through a non-volatile-qualified reference, resulting in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior):
+
+```cpp
+#include
+
+void func(void) {
+ static volatile int **ipp;
+ static int *ip;
+ static volatile int i = 0;
+
+ printf("i = %d.\n", i);
+
+ ipp = &ip; /* May produce a warning diagnostic */
+ ipp = (int**) &ip; /* Constraint violation; may produce a warning diagnostic */
+ *ipp = &i; /* Valid */
+ if (*ip != 0) { /* Valid */
+ /* ... */
+ }
+}
+```
+The assignment `ipp = &ip` is not safe because it allows the valid code that follows to reference the value of the volatile object `i` through the non-volatile-qualified reference `ip`. In this example, the compiler may optimize out the entire `if` block because `*ip != 0` must be false if the object to which `ip` points is not volatile.
+
+**Implementation Details**
+
+This example compiles without warning on Microsoft Visual Studio 2013 when compiled in C mode (`/TC`) but causes errors when compiled in C++ mode (`/TP`).
+
+GCC 4.8.1 generates a warning but compiles successfully.
+
+## Compliant Solution
+
+In this compliant solution, `ip` is declared `volatile`:
+
+```cpp
+#include
+
+void func(void) {
+ static volatile int **ipp;
+ static volatile int *ip;
+ static volatile int i = 0;
+
+ printf("i = %d.\n", i);
+
+ ipp = &ip;
+ *ipp = &i;
+ if (*ip != 0) {
+ /* ... */
+ }
+
+}
+```
+
+## Risk Assessment
+
+Accessing an object with a volatile-qualified type through a reference with a non-volatile-qualified type is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior).
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP32-C | Low | Likely | Medium | P6 | L2 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | pointer-qualifier-cast-volatile pointer-qualifier-cast-volatile-implicit | Supported indirectly via MISRA C 2012 Rule 11.8 |
Axivion Bauhaus Suite | 7.2.0 | CertC-EXP32 | Fully implemented |
Clang | 3.9 | -Wincompatible-pointer-types-discards-qualifiers | |
Compass/ROSE | | | |
Coverity | 2017.07 | MISRA C 2012 Rule 11.8 | Implemented |
GCC | 4.3.5 | | Can detect violations of this rule when the -Wcast-qual flag is used |
Helix QAC | 2022.4 | C0312, C0562, C0563, C0673, C0674 | |
Klocwork | 2022.4 | CERT.EXPR.VOLATILE.ADDR CERT.EXPR.VOLATILE.ADDR.PARAM CERT.EXPR.VOLATILE.PTRPTR | |
LDRA tool suite | 9.7.1 | 344 S | Partially implemented |
Parasoft C/C++test | 2022.2 | CERT_C-EXP32-a | A cast shall not remove any 'const' or 'volatile' qualification from the type of a pointer or reference |
Polyspace Bug Finder | | CERT C: Rule EXP32-C | Checks for cast to pointer that removes const or volatile qualification (rule fully covered) |
PRQA QA-C | 9.7 | 0312,562,563,673,674 | Fully implemented |
RuleChecker | 22.04 | pointer-qualifier-cast-volatile pointer-qualifier-cast-volatile-implicit | Supported indirectly via MISRA C 2012 Rule 11.8 |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP32-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+In limited cases, this query can raise false-positives for assignment of volatile objects and subsequent accesses of those objects via non-volatile pointers.
+
+## References
+
+* CERT-C: [EXP32-C: Do not access a volatile object through a nonvolatile reference](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP32-C/DoNotAccessVolatileObjectWithNonVolatileReference.ql b/c/cert/src/rules/EXP32-C/DoNotAccessVolatileObjectWithNonVolatileReference.ql
new file mode 100644
index 0000000000..891b93bcda
--- /dev/null
+++ b/c/cert/src/rules/EXP32-C/DoNotAccessVolatileObjectWithNonVolatileReference.ql
@@ -0,0 +1,95 @@
+/**
+ * @id c/cert/do-not-access-volatile-object-with-non-volatile-reference
+ * @name EXP32-C: Do not access a volatile object through a nonvolatile reference
+ * @description If an an object defined with a volatile-qualified type is referred to with an lvalue
+ * of a non-volatile-qualified type, the behavior is undefined.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/exp32-c
+ * correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p6
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import semmle.code.cpp.controlflow.Dereferenced
+
+/**
+ * An expression involving volatile-qualified types that results in undefined behavior.
+ */
+abstract class UndefinedVolatilePointerExpr extends Expr {
+ /**
+ * Gets a descriptive string describing the type of expression and undefined behavior.
+ */
+ abstract string getMessage();
+}
+
+/**
+ * Gets the depth of a pointer's base type's volatile qualifier
+ */
+int getAVolatileDepth(Type type) {
+ type.isVolatile() and result = 1
+ or
+ result = getAVolatileDepth(type.(DerivedType).getBaseType()) + 1
+}
+
+/**
+ * A `Cast` which converts from a pointer to a volatile-qualified type
+ * to a pointer to a non-volatile-qualified type.
+ */
+class CastFromVolatileToNonVolatileBaseType extends Cast, UndefinedVolatilePointerExpr {
+ CastFromVolatileToNonVolatileBaseType() {
+ exists(int i |
+ i = getAVolatileDepth(this.getExpr().getType()) and
+ not i = getAVolatileDepth(this.getActualType())
+ )
+ }
+
+ override string getMessage() {
+ result = "Cast of object with a volatile-qualified type to a non-volatile-qualified type."
+ }
+}
+
+/**
+ * Holds if `va` has a subsequent `VariableAccess` which is dereferenced after access
+ */
+bindingset[va]
+predicate hasSubsequentDereference(VariableAccess va) {
+ dereferenced(pragma[only_bind_out](va).getASuccessor+())
+}
+
+/**
+ * An `AssignExpr` with an *lvalue* that is a pointer to a volatile base type and
+ * and *rvalue* that is not also a pointer to a volatile base type.
+ */
+class NonVolatileObjectAssignedToVolatilePointer extends AssignExpr, UndefinedVolatilePointerExpr {
+ NonVolatileObjectAssignedToVolatilePointer() {
+ exists(int i |
+ not i = getAVolatileDepth(this.getRValue().getType()) and
+ i = getAVolatileDepth(this.getLValue().(VariableAccess).getTarget().getType())
+ ) and
+ // Checks for subsequent accesses to the underlying object via the original non-volatile
+ // pointer assigned to the volatile pointer. This heuristic can cause false-positives
+ // in certain instances which require more advanced reachability analysis, e.g. loops and scope
+ // considerations that this simple forward traversal of the control-flow graph does not account for.
+ exists(VariableAccess va |
+ va = this.getRValue().getAChild*().(VariableAccess).getTarget().getAnAccess() and
+ hasSubsequentDereference(va)
+ )
+ }
+
+ override string getMessage() {
+ result =
+ "Assignment indicates a volatile object, but a later access of the object occurs via a non-volatile pointer."
+ }
+}
+
+from UndefinedVolatilePointerExpr e
+where not isExcluded(e, Pointers3Package::doNotAccessVolatileObjectWithNonVolatileReferenceQuery())
+select e, e.getMessage()
diff --git a/c/cert/src/rules/EXP33-C/DoNotReadUninitializedMemory.md b/c/cert/src/rules/EXP33-C/DoNotReadUninitializedMemory.md
new file mode 100644
index 0000000000..6328cb86d3
--- /dev/null
+++ b/c/cert/src/rules/EXP33-C/DoNotReadUninitializedMemory.md
@@ -0,0 +1,417 @@
+# EXP33-C: Do not read uninitialized memory
+
+This query implements the CERT-C rule EXP33-C:
+
+> Do not read uninitialized memory
+
+
+## Description
+
+Local, automatic variables assume unexpected values if they are read before they are initialized. The C Standard, 6.7.9, paragraph 10, specifies \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\]
+
+> If an object that has automatic storage duration is not initialized explicitly, its value is [indeterminate](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue).
+
+
+See [undefined behavior 11](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_11).
+
+When local, automatic variables are stored on the program stack, for example, their values default to whichever values are currently stored in stack memory.
+
+Additionally, some dynamic memory allocation functions do not initialize the contents of the memory they allocate.
+
+ Function | Initialization |
aligned_alloc() | Does not perform initialization |
calloc() | Zero-initializes allocated memory |
malloc() | Does not perform initialization |
realloc() | Copies contents from original pointer; may not initialize all memory |
+Uninitialized automatic variables or dynamically allocated memory has [indeterminate values](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue), which for objects of some types, can be a [trap representation](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-traprepresentation). Reading such trap representations is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior); it can cause a program to behave in an [unexpected](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-unexpectedbehavior) manner and provide an avenue for attack. (See [undefined behavior 10](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_10) and [undefined behavior 12](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_12).) In many cases, compilers issue a warning diagnostic message when reading uninitialized variables. (See [MSC00-C. Compile cleanly at high warning levels](https://wiki.sei.cmu.edu/confluence/display/c/MSC00-C.+Compile+cleanly+at+high+warning+levels) for more information.)
+
+
+## Noncompliant Code Example (Return-by-Reference)
+
+In this noncompliant code example, the `set_flag()` function is intended to set the parameter, `sign_flag`, to the sign of `number`. However, the programmer neglected to account for the case where `number` is equal to `0`. Because the local variable `sign` is uninitialized when calling `set_flag()` and is never written to by `set_flag()`, the comparison operation exhibits [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) when reading `sign`.
+
+```cpp
+void set_flag(int number, int *sign_flag) {
+ if (NULL == sign_flag) {
+ return;
+ }
+
+ if (number > 0) {
+ *sign_flag = 1;
+ } else if (number < 0) {
+ *sign_flag = -1;
+ }
+}
+
+int is_negative(int number) {
+ int sign;
+ set_flag(number, &sign);
+ return sign < 0;
+}
+
+```
+Some compilers assume that when the address of an uninitialized variable is passed to a function, the variable is initialized within that function. Because compilers frequently fail to diagnose any resulting failure to initialize the variable, the programmer must apply additional scrutiny to ensure the correctness of the code.
+
+This defect results from a failure to consider all possible data states. (See [MSC01-C. Strive for logical completeness](https://wiki.sei.cmu.edu/confluence/display/c/MSC01-C.+Strive+for+logical+completeness) for more information.)
+
+## Compliant Solution (Return-by-Reference)
+
+This compliant solution trivially repairs the problem by accounting for the possibility that `number` can be equal to 0.
+
+Although compilers and [static analysis](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-staticanalysis) tools often detect uses of uninitialized variables when they have access to the source code, diagnosing the problem is difficult or impossible when either the initialization or the use takes place in object code for which the source code is inaccessible. Unless doing so is prohibitive for performance reasons, an additional defense-in-depth practice worth considering is to initialize local variables immediately after declaration.
+
+```cpp
+void set_flag(int number, int *sign_flag) {
+ if (NULL == sign_flag) {
+ return;
+ }
+
+ /* Account for number being 0 */
+ if (number >= 0) {
+ *sign_flag = 1;
+ } else {
+ *sign_flag = -1;
+ }
+}
+
+int is_negative(int number) {
+ int sign = 0; /* Initialize for defense-in-depth */
+ set_flag(number, &sign);
+ return sign < 0;
+}
+
+```
+
+## Noncompliant Code Example (Uninitialized Local)
+
+In this noncompliant code example, the programmer mistakenly fails to set the local variable `error_log` to the `msg` argument in the `report_error()` function \[[Mercy 2006](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-mercy06)\]. Because `error_log` has not been initialized, an [indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue) is read. The `sprintf()` call copies data from the arbitrary location pointed to by the indeterminate `error_log` variable until a null byte is reached, which can result in a buffer overflow.
+
+```cpp
+#include
+
+/* Get username and password from user, return -1 on error */
+extern int do_auth(void);
+enum { BUFFERSIZE = 24 };
+void report_error(const char *msg) {
+ const char *error_log;
+ char buffer[BUFFERSIZE];
+
+ sprintf(buffer, "Error: %s", error_log);
+ printf("%s\n", buffer);
+}
+
+int main(void) {
+ if (do_auth() == -1) {
+ report_error("Unable to login");
+ }
+ return 0;
+}
+
+```
+
+## Noncompliant Code Example (Uninitialized Local)
+
+In this noncompliant code example, the `report_error()` function has been modified so that `error_log` is properly initialized:
+
+```cpp
+#include
+enum { BUFFERSIZE = 24 };
+void report_error(const char *msg) {
+ const char *error_log = msg;
+ char buffer[BUFFERSIZE];
+
+ sprintf(buffer, "Error: %s", error_log);
+ printf("%s\n", buffer);
+}
+
+```
+This example remains problematic because a buffer overflow will occur if the null-terminated byte string referenced by `msg` is greater than 17 characters, including the null terminator. (See [STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator](https://wiki.sei.cmu.edu/confluence/display/c/STR31-C.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator) for more information.)
+
+## Compliant Solution (Uninitialized Local)
+
+In this compliant solution, the buffer overflow is eliminated by calling the `snprintf()` function:
+
+```cpp
+#include
+enum { BUFFERSIZE = 24 };
+void report_error(const char *msg) {
+ char buffer[BUFFERSIZE];
+
+ if (0 < snprintf(buffer, BUFFERSIZE, "Error: %s", msg))
+ printf("%s\n", buffer);
+ else
+ puts("Unknown error");
+}
+
+```
+
+## Compliant Solution (Uninitialized Local)
+
+A less error-prone compliant solution is to simply print the error message directly instead of using an intermediate buffer:
+
+```cpp
+#include
+
+void report_error(const char *msg) {
+ printf("Error: %s\n", msg);
+}
+
+```
+
+## Noncompliant Code Example (mbstate_t)
+
+In this noncompliant code example, the function `mbrlen()` is passed the address of an automatic `mbstate_t` object that has not been properly initialized. This is [undefined behavior 200](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_200) because `mbrlen()` dereferences and reads its third argument.
+
+```cpp
+#include
+#include
+
+void func(const char *mbs) {
+ size_t len;
+ mbstate_t state;
+
+ len = mbrlen(mbs, strlen(mbs), &state);
+}
+
+```
+
+## Compliant Solution (mbstate_t)
+
+Before being passed to a multibyte conversion function, an `mbstate_t` object must be either initialized to the initial conversion state or set to a value that corresponds to the most recent shift state by a prior call to a multibyte conversion function. This compliant solution sets the `mbstate_t` object to the initial conversion state by setting it to all zeros:
+
+```cpp
+#include
+#include
+
+void func(const char *mbs) {
+ size_t len;
+ mbstate_t state;
+
+ memset(&state, 0, sizeof(state));
+ len = mbrlen(mbs, strlen(mbs), &state);
+}
+
+```
+
+## Noncompliant Code Example (POSIX, Entropy)
+
+In this noncompliant code example described in "[More Randomness or Less](http://kqueue.org/blog/2012/06/25/more-randomness-or-less/)" \[[Wang 2012](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Wang12)\], the process ID, time of day, and uninitialized memory `junk` is used to seed a random number generator. This behavior is characteristic of some distributions derived from Debian Linux that use uninitialized memory as a source of entropy because the value stored in `junk` is indeterminate. However, because accessing an [indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue) is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior), compilers may optimize out the uninitialized variable access completely, leaving only the time and process ID and resulting in a loss of desired entropy.
+
+```cpp
+#include
+#include
+#include
+#include
+
+void func(void) {
+ struct timeval tv;
+ unsigned long junk;
+
+ gettimeofday(&tv, NULL);
+ srandom((getpid() << 16) ^ tv.tv_sec ^ tv.tv_usec ^ junk);
+}
+```
+In security protocols that rely on unpredictability, such as RSA encryption, a loss in entropy results in a less secure system.
+
+## Compliant Solution (POSIX, Entropy)
+
+This compliant solution seeds the random number generator by using the CPU clock and the real-time clock instead of reading uninitialized memory:
+
+```cpp
+#include
+#include
+#include
+#include
+
+void func(void) {
+ double cpu_time;
+ struct timeval tv;
+
+ cpu_time = ((double) clock()) / CLOCKS_PER_SEC;
+ gettimeofday(&tv, NULL);
+ srandom((getpid() << 16) ^ tv.tv_sec ^ tv.tv_usec ^ cpu_time);
+}
+```
+
+## Noncompliant Code Example (realloc())
+
+The `realloc()` function changes the size of a dynamically allocated memory object. The initial `size` bytes of the returned memory object are unchanged, but any newly added space is uninitialized, and its value is [indeterminate](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue). As in the case of `malloc()`, accessing memory beyond the size of the original object is [undefined behavior 181](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_171).
+
+It is the programmer's responsibility to ensure that any memory allocated with `malloc()` and `realloc()` is properly initialized before it is used.
+
+In this noncompliant code example, an array is allocated with `malloc()` and properly initialized. At a later point, the array is grown to a larger size but not initialized beyond what the original array contained. Subsequently accessing the uninitialized bytes in the new array is undefined behavior.
+
+```cpp
+#include
+#include
+enum { OLD_SIZE = 10, NEW_SIZE = 20 };
+
+int *resize_array(int *array, size_t count) {
+ if (0 == count) {
+ return 0;
+ }
+
+ int *ret = (int *)realloc(array, count * sizeof(int));
+ if (!ret) {
+ free(array);
+ return 0;
+ }
+
+ return ret;
+}
+
+void func(void) {
+
+ int *array = (int *)malloc(OLD_SIZE * sizeof(int));
+ if (0 == array) {
+ /* Handle error */
+ }
+
+ for (size_t i = 0; i < OLD_SIZE; ++i) {
+ array[i] = i;
+ }
+
+ array = resize_array(array, NEW_SIZE);
+ if (0 == array) {
+ /* Handle error */
+ }
+
+ for (size_t i = 0; i < NEW_SIZE; ++i) {
+ printf("%d ", array[i]);
+ }
+}
+```
+
+## Compliant Solution (realloc())
+
+In this compliant solution, the `resize_array()` helper function takes a second parameter for the old size of the array so that it can initialize any newly allocated elements:
+
+```cpp
+#include
+#include
+#include
+
+enum { OLD_SIZE = 10, NEW_SIZE = 20 };
+
+int *resize_array(int *array, size_t old_count, size_t new_count) {
+ if (0 == new_count) {
+ return 0;
+ }
+
+ int *ret = (int *)realloc(array, new_count * sizeof(int));
+ if (!ret) {
+ free(array);
+ return 0;
+ }
+
+ if (new_count > old_count) {
+ memset(ret + old_count, 0, (new_count - old_count) * sizeof(int));
+ }
+
+ return ret;
+}
+
+void func(void) {
+
+ int *array = (int *)malloc(OLD_SIZE * sizeof(int));
+ if (0 == array) {
+ /* Handle error */
+ }
+
+ for (size_t i = 0; i < OLD_SIZE; ++i) {
+ array[i] = i;
+ }
+
+ array = resize_array(array, OLD_SIZE, NEW_SIZE);
+ if (0 == array) {
+ /* Handle error */
+ }
+
+ for (size_t i = 0; i < NEW_SIZE; ++i) {
+ printf("%d ", array[i]);
+ }
+}
+```
+
+## Exceptions
+
+**EXP33-C-EX1:** Reading uninitialized memory by an [lvalue](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-lvalue) of type `unsigned char` that could not have been declared with the `register` storage class does not trigger [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). The `unsigned char` type is defined to not have a trap representation, which allows for moving bytes without knowing if they are initialized. (See the C Standard, 6.2.6.1, paragraph 3.) The requirement that `register` could not have been used (not merely that it was not used) is because on some architectures, such as the Intel Itanium, registers have a bit to indicate whether or not they have been initialized. The C Standard, 6.3.2.1, paragraph 2, allows such [implementations](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) to cause a trap for an object that never had its address taken and is stored in a register if such an object is referred to in any way.
+
+## Risk Assessment
+
+Reading uninitialized variables is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) and can result in [unexpected program behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-unexpectedbehavior). In some cases, these [security flaws](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-securityflaw) may allow the execution of arbitrary code.
+
+Reading uninitialized variables for creating entropy is problematic because these memory accesses can be removed by compiler optimization. [VU\#925211](http://www.kb.cert.org/vuls/id/925211) is an example of a [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) caused by this coding error.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP33-C | High | Probable | Medium | P12 | L1 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | uninitialized-local-read uninitialized-variable-use | Fully checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-EXP33 | |
CodeSonar | 7.2p0 | LANG.MEM.UVAR | Uninitialized variable |
Compass/ROSE | | | Automatically detects simple violations of this rule, although it may return some false positives. It may not catch more complex violations, such as initialization within functions taking uninitialized variables as arguments. It does catch the second noncompliant code example, and can be extended to catch the first as well |
Coverity | 2017.07 | UNINIT | Implemented |
Cppcheck | 1.66 | uninitvaruninitdatauninitstringuninitMemberVaruninitStructMember | Detects uninitialized variables, uninitialized pointers, uninitialized struct members, and uninitialized array elements (However, if one element is initialized, then cppcheck assumes the array is initialized.) There are FN compared to some other tools because Cppcheck tries to avoid FP in impossible paths. |
GCC | 4.3.5 | | Can detect some violations of this rule when the -Wuninitialized flag is used |
Helix QAC | 2022.4 | DF2726, DF2727, DF2728, DF2961, DF2962, DF2963, DF2966, DF2967, DF2968, DF2971, DF2972, DF2973, DF2976, DF2977, DF2978 | |
Klocwork | 2022.4 | UNINIT.HEAP.MIGHT UNINIT.HEAP.MUST UNINIT.STACK.ARRAY.MIGHT UNINIT.STACK.ARRAY.MUST UNINIT.STACK.ARRAY.PARTIAL.MUST UNINIT.STACK.MIGHT UNINIT.STACK.MUST UNINIT.CTOR.MIGHT UNINIT.CTOR.MUST | |
LDRA tool suite | 9.7.1 | 53 D, 69 D, 631 S, 652 S | Fully implemented |
Parasoft C/C++test | 2022.2 | CERT_C-EXP33-a | Avoid use before initialization |
Parasoft Insure++ | 2022.2 | | Runtime analysis |
PC-lint Plus | 1.4 | 530, 603, 644, 901 | Fully supported |
Polyspace Bug Finder | R2022b | CERT C: Rule EXP33-C | Checks for: Non-initialized variableon-initialized variable, non-initialized pointeron-initialized pointer. Rule partially covered |
PRQA QA-C | 9.7 | 2726, 2727, 2728, 2961, 2962, 2963, 2966, 2967, 2968, 2971, 2972, 2973, 2976, 2977, 2978 | Fully implemented |
PRQA QA-C++ | 4.4 | 2961, 2962, 2963, 2966, 2967, 2968, 2971, 2972, 2973, 2976, 2977, 2978 | |
PVS-Studio | 7.22 | V573 , V614 , V670 , V679 , V1050 | |
RuleChecker | 22.04 | uninitialized-local-read | Partially checked |
Splint | 3.1.1 | | |
TrustInSoft Analyzer | 1.38 | initialisation | Exhaustively verified (see one compliant and one non-compliant example ). |
+
+
+## Related Vulnerabilities
+
+[CVE-2009-1888](http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-1888) results from a violation of this rule. Some versions of SAMBA (up to 3.3.5) call a function that takes in two potentially uninitialized variables involving access rights. An attacker can [exploit](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-exploit) these coding errors to bypass the access control list and gain access to protected files \[[xorl 2009](http://xorl.wordpress.com/2009/06/26/cve-2009-1888-samba-acls-uninitialized-memory-read/)\].
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP33-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-119 and EXP33-C**
+
+* Intersection( CWE-119, EXP33-C) = Ø
+* EXP33-C is about reading uninitialized memory, but this memory is considered part of a valid buffer (on the stack, or returned by a heap function). No buffer overflow is involved.
+**CWE-676 and EXP33-C**
+* Intersection( CWE-676, EXP33-C) = Ø
+* EXP33-C implies that memory allocation functions (e.g., malloc()) are dangerous because they do not initialize the memory they reserve. However, the danger is not in their invocation, but rather reading their returned memory without initializing it.
+**CWE-758 and EXP33-C**
+
+Independent( INT34-C, INT36-C, MSC37-C, FLP32-C, EXP33-C, EXP30-C, ERR34-C, ARR32-C)
+
+CWE-758 = Union( EXP33-C, list) where list =
+
+* Undefined behavior that results from anything other than reading uninitialized memory
+**CWE-665 and EXP33-C**
+
+Intersection( CWE-665, EXP33-C) = Ø
+
+CWE-665 is about correctly initializing items (usually objects), not reading them later. EXP33-C is about reading memory later (that has not been initialized).
+
+**CWE-908 and EXP33-C**
+
+CWE-908 = Union( EXP33-C, list) where list =
+
+* Use of uninitialized items besides raw memory (objects, disk space, etc)
+New CWE-CERT mappings:
+
+**CWE-123 and EXP33-C**
+
+Intersection( CWE-123, EXP33-C) = Ø
+
+EXP33-C is only about reading uninitialized memory, not writing, whereas CWE-123 is about writing.
+
+**CWE-824 and EXP33-C**
+
+EXP33-C = Union( CWE-824, list) where list =
+
+* Read of uninitialized memory that does not represent a pointer
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [EXP33-C: Do not read uninitialized memory](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP33-C/DoNotReadUninitializedMemory.ql b/c/cert/src/rules/EXP33-C/DoNotReadUninitializedMemory.ql
new file mode 100644
index 0000000000..94deea912e
--- /dev/null
+++ b/c/cert/src/rules/EXP33-C/DoNotReadUninitializedMemory.ql
@@ -0,0 +1,28 @@
+/**
+ * @id c/cert/do-not-read-uninitialized-memory
+ * @name EXP33-C: Do not read uninitialized memory
+ * @description Using the value of an object with automatic storage duration while it is
+ * indeterminate is undefined behavior.
+ * @kind problem
+ * @precision medium
+ * @problem.severity error
+ * @tags external/cert/id/exp33-c
+ * correctness
+ * security
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p12
+ * external/cert/level/l1
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.rules.readofuninitializedmemory.ReadOfUninitializedMemory
+
+class DoNotReadUninitializedMemoryQuery extends ReadOfUninitializedMemorySharedQuery {
+ DoNotReadUninitializedMemoryQuery() {
+ this = InvalidMemory1Package::doNotReadUninitializedMemoryQuery()
+ }
+}
diff --git a/c/cert/src/rules/EXP34-C/DoNotDereferenceNullPointers.md b/c/cert/src/rules/EXP34-C/DoNotDereferenceNullPointers.md
new file mode 100644
index 0000000000..40b0d59a4a
--- /dev/null
+++ b/c/cert/src/rules/EXP34-C/DoNotDereferenceNullPointers.md
@@ -0,0 +1,219 @@
+# EXP34-C: Do not dereference null pointers
+
+This query implements the CERT-C rule EXP34-C:
+
+> Do not dereference null pointers
+
+
+## Description
+
+Dereferencing a null pointer is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior).
+
+On many platforms, dereferencing a null pointer results in [abnormal program termination](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-abnormaltermination), but this is not required by the standard. See "[Clever Attack Exploits Fully-Patched Linux Kernel](http://www.theregister.co.uk/2009/07/17/linux_kernel_exploit/)" \[[Goodin 2009](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Goodin2009)\] for an example of a code execution [exploit](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-exploit) that resulted from a null pointer dereference.
+
+## Noncompliant Code Example
+
+This noncompliant code example is derived from a real-world example taken from a vulnerable version of the `libpng` library as deployed on a popular ARM-based cell phone \[[Jack 2007](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Jack07)\]. The `libpng` library allows applications to read, create, and manipulate PNG (Portable Network Graphics) raster image files. The `libpng` library implements its own wrapper to `malloc()` that returns a null pointer on error or on being passed a 0-byte-length argument.
+
+This code also violates [ERR33-C. Detect and handle standard library errors](https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors).
+
+```cpp
+#include /* From libpng */
+#include
+
+void func(png_structp png_ptr, int length, const void *user_data) {
+ png_charp chunkdata;
+ chunkdata = (png_charp)png_malloc(png_ptr, length + 1);
+ /* ... */
+ memcpy(chunkdata, user_data, length);
+ /* ... */
+ }
+```
+If `length` has the value `−1`, the addition yields 0, and `png_malloc()` subsequently returns a null pointer, which is assigned to `chunkdata`. The `chunkdata` pointer is later used as a destination argument in a call to `memcpy()`, resulting in user-defined data overwriting memory starting at address 0. In the case of the ARM and XScale architectures, the `0x0` address is mapped in memory and serves as the exception vector table; consequently, dereferencing `0x0` did not cause an [abnormal program termination](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-abnormaltermination).
+
+## Compliant Solution
+
+This compliant solution ensures that the pointer returned by `png_malloc()` is not null. It also uses the unsigned type `size_t` to pass the `length` parameter, ensuring that negative values are not passed to `func()`.
+
+This solution also ensures that the `user_data` pointer is not null. Passing a null pointer to memcpy() would produce undefined behavior, even if the number of bytes to copy were 0. The `user_data` pointer could be invalid in other ways, such as pointing to freed memory. However there is no portable way to verify that the pointer is valid, other than checking for null.
+
+```cpp
+#include /* From libpng */
+#include
+
+ void func(png_structp png_ptr, size_t length, const void *user_data) {
+ png_charp chunkdata;
+ if (length == SIZE_MAX) {
+ /* Handle error */
+ }
+ if (NULL == user_data) {
+ /* Handle error */
+ }
+ chunkdata = (png_charp)png_malloc(png_ptr, length + 1);
+ if (NULL == chunkdata) {
+ /* Handle error */
+ }
+ /* ... */
+ memcpy(chunkdata, user_data, length);
+ /* ... */
+
+ }
+```
+
+## Noncompliant Code Example
+
+In this noncompliant code example, `input_str` is copied into dynamically allocated memory referenced by `c_str`. If `malloc()` fails, it returns a null pointer that is assigned to `c_str`. When `c_str` is dereferenced in `memcpy()`, the program exhibits [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). Additionally, if `input_str` is a null pointer, the call to `strlen()` dereferences a null pointer, also resulting in undefined behavior. This code also violates [ERR33-C. Detect and handle standard library errors](https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors).
+
+```cpp
+#include
+#include
+
+void f(const char *input_str) {
+ size_t size = strlen(input_str) + 1;
+ char *c_str = (char *)malloc(size);
+ memcpy(c_str, input_str, size);
+ /* ... */
+ free(c_str);
+ c_str = NULL;
+ /* ... */
+}
+```
+
+## Compliant Solution
+
+This compliant solution ensures that both `input_str` and the pointer returned by `malloc()` are not null:
+
+```cpp
+#include
+#include
+
+void f(const char *input_str) {
+ size_t size;
+ char *c_str;
+
+ if (NULL == input_str) {
+ /* Handle error */
+ }
+
+ size = strlen(input_str) + 1;
+ c_str = (char *)malloc(size);
+ if (NULL == c_str) {
+ /* Handle error */
+ }
+ memcpy(c_str, input_str, size);
+ /* ... */
+ free(c_str);
+ c_str = NULL;
+ /* ... */
+}
+```
+
+## Noncompliant Code Example
+
+This noncompliant code example is from a version of `drivers/net/tun.c` and affects Linux kernel 2.6.30 \[[Goodin 2009](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Goodin2009)\]:
+
+```cpp
+static unsigned int tun_chr_poll(struct file *file, poll_table *wait) {
+ struct tun_file *tfile = file->private_data;
+ struct tun_struct *tun = __tun_get(tfile);
+ struct sock *sk = tun->sk;
+ unsigned int mask = 0;
+
+ if (!tun)
+ return POLLERR;
+
+ DBG(KERN_INFO "%s: tun_chr_poll\n", tun->dev->name);
+
+ poll_wait(file, &tun->socket.wait, wait);
+
+ if (!skb_queue_empty(&tun->readq))
+ mask |= POLLIN | POLLRDNORM;
+
+ if (sock_writeable(sk) ||
+ (!test_and_set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags) &&
+ sock_writeable(sk)))
+ mask |= POLLOUT | POLLWRNORM;
+
+ if (tun->dev->reg_state != NETREG_REGISTERED)
+ mask = POLLERR;
+
+ tun_put(tun);
+ return mask;
+}
+
+```
+The `sk` pointer is initialized to `tun->sk` before checking if `tun` is a null pointer. Because null pointer dereferencing is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior), the compiler (GCC in this case) can optimize away the `if (!tun)` check because it is performed after `tun->sk` is accessed, implying that `tun` is non-null. As a result, this noncompliant code example is vulnerable to a null pointer dereference exploit, because null pointer dereferencing can be permitted on several platforms, for example, by using `mmap(2)` with the `MAP_FIXED` flag on Linux and Mac OS X, or by using the `shmat()` POSIX function with the `SHM_RND` flag \[[Liu 2009](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Liu2009)\].
+
+## Compliant Solution
+
+This compliant solution eliminates the null pointer deference by initializing `sk` to `tun->sk` following the null pointer check. It also adds assertions to document that certain other pointers must not be null.
+
+```cpp
+static unsigned int tun_chr_poll(struct file *file, poll_table *wait) {
+ assert(file);
+ struct tun_file *tfile = file->private_data;
+ struct tun_struct *tun = __tun_get(tfile);
+ struct sock *sk;
+ unsigned int mask = 0;
+
+ if (!tun)
+ return POLLERR;
+ assert(tun->dev);
+ sk = tun->sk;
+ assert(sk);
+ assert(sk->socket);
+ /* The remaining code is omitted because it is unchanged... */
+}
+
+```
+
+## Risk Assessment
+
+Dereferencing a null pointer is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior), typically [abnormal program termination](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-abnormaltermination). In some situations, however, dereferencing a null pointer can lead to the execution of arbitrary code \[[Jack 2007](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Jack07), [van Sprundel 2006](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-vanSprundel06)\]. The indicated severity is for this more severe case; on platforms where it is not possible to exploit a null pointer dereference to execute arbitrary code, the actual severity is low.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP34-C | High | Likely | Medium | P18 | L1 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | null-dereferencing | Fully checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-EXP34 | |
CodeSonar | 7.2p0 | LANG.MEM.NPD LANG.STRUCT.NTAD LANG.STRUCT.UPD | Null pointer dereference Null test after dereference Unchecked parameter dereference |
Compass/ROSE | | | Can detect violations of this rule. In particular, ROSE ensures that any pointer returned by malloc() , calloc() , or realloc() is first checked for NULL before being used (otherwise, it is free() -ed). ROSE does not handle cases where an allocation is assigned to an lvalue that is not a variable (such as a struct member or C++ function call returning a reference) |
Coverity | 2017.07 | CHECKED_RETURN NULL_RETURNS REVERSE_INULL FORWARD_NULL | Finds instances where a pointer is checked against NULL and then later dereferenced Identifies functions that can return a null pointer but are not checked Identifies code that dereferences a pointer and then checks the pointer against NULL Can find the instances where NULL is explicitly dereferenced or a pointer is checked against NULL but then dereferenced anyway. Coverity Prevent cannot discover all violations of this rule, so further verification is necessary |
Cppcheck | 1.66 | nullPointer, nullPointerDefaultArg, nullPointerRedundantCheck | Context sensitive analysis Detects when NULL is dereferenced (Array of pointers is not checked. Pointer members in structs are not checked.) Finds instances where a pointer is checked against NULL and then later dereferenced Identifies code that dereferences a pointer and then checks the pointer against NULL Does not guess that return values from malloc() , strchr() , etc., can be NULL (The return value from malloc() is NULL only if there is OOMo and the dev might not care to handle that. The return value from strchr() is often NULL , but the dev might know that a specific strchr() function call will not return NULL .) |
Helix QAC | 2022.4 | DF2810, DF2811, DF2812, DF2813 | |
Klocwork | 2022.4 | NPD.CHECK.CALL.MIGHT NPD.CHECK.CALL.MUST NPD.CHECK.MIGHT NPD.CHECK.MUST NPD.CONST.CALL NPD.CONST.DEREF NPD.FUNC.CALL.MIGHT NPD.FUNC.CALL.MUST NPD.FUNC.MIGHT NPD.FUNC.MUST NPD.GEN.CALL.MIGHT NPD.GEN.CALL.MUST NPD.GEN.MIGHT NPD.GEN.MUST RNPD.CALL RNPD.DEREF | |
LDRA tool suite | 9.7.1 | 45 D, 123 D, 128 D, 129 D, 130 D, 131 D, 652 S | Fully implemented |
Parasoft C/C++test | 2022.2 | CERT_C-EXP34-a | Avoid null pointer dereferencing |
Parasoft Insure++ | | | Runtime analysis |
PC-lint Plus | 1.4 | 413, 418, 444, 613, 668 | Partially supported |
Polyspace Bug Finder | | CERT C: Rule EXP34-C | Checks for use of null pointers (rule partially covered) |
PRQA QA-C | 9.7 | 2810, 2811, 2812, 2813 | Fully implemented |
PRQA QA-C++ | 4.4 | 2810, 2811, 2812, 2813 | |
PVS-Studio | 7.22 | V522 , V595 , V664 , V713 , V1004 | |
SonarQube C/C++ Plugin | 3.11 | S2259 | |
Splint | 3.1.1 | | |
TrustInSoft Analyzer | 1.38 | mem_access | Exhaustively verified (see one compliant and one non-compliant example ). |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP34-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-690 and EXP34-C**
+
+EXP34-C = Union( CWE-690, list) where list =
+
+* Dereferencing null pointers that were not returned by a function
+**CWE-252 and EXP34-C**
+
+Intersection( CWE-252, EXP34-C) = Ø
+
+EXP34-C is a common consequence of ignoring function return values, but it is a distinct error, and can occur in other scenarios too.
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [EXP34-C: Do not dereference null pointers](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP34-C/DoNotDereferenceNullPointers.ql b/c/cert/src/rules/EXP34-C/DoNotDereferenceNullPointers.ql
new file mode 100644
index 0000000000..51b93c8000
--- /dev/null
+++ b/c/cert/src/rules/EXP34-C/DoNotDereferenceNullPointers.ql
@@ -0,0 +1,26 @@
+/**
+ * @id c/cert/do-not-dereference-null-pointers
+ * @name EXP34-C: Do not dereference null pointers
+ * @description Dereferencing a null pointer leads to undefined behavior.
+ * @kind problem
+ * @precision medium
+ * @problem.severity error
+ * @tags external/cert/id/exp34-c
+ * correctness
+ * external/cert/severity/high
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p18
+ * external/cert/level/l1
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.rules.dereferenceofnullpointer.DereferenceOfNullPointer
+
+class DoNotDereferenceNullPointersQuery extends DereferenceOfNullPointerSharedQuery {
+ DoNotDereferenceNullPointersQuery() {
+ this = InvalidMemory1Package::doNotDereferenceNullPointersQuery()
+ }
+}
diff --git a/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.md b/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.md
new file mode 100644
index 0000000000..58ff1b03cf
--- /dev/null
+++ b/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.md
@@ -0,0 +1,225 @@
+# EXP35-C: Do not modify objects with temporary lifetime
+
+This query implements the CERT-C rule EXP35-C:
+
+> Do not modify objects with temporary lifetime
+
+
+## Description
+
+The C11 Standard \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\] introduced a new term: *temporary lifetime*. Modifying an object with temporary lifetime is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). According to subclause 6.2.4, paragraph 8
+
+> A non-lvalue expression with structure or union type, where the structure or union contains a member with array type (including, recursively, members of all contained structures and unions) refers to an object with automatic storage duration and *temporary*lifetime. Its lifetime begins when the expression is evaluated and its initial value is the value of the expression. Its lifetime ends when the evaluation of the containing full expression or full declarator ends. Any attempt to modify an object with temporary lifetime results in undefined behavior.
+
+
+This definition differs from the C99 Standard (which defines modifying the result of a function call or accessing it after the next sequence point as undefined behavior) because a temporary object's lifetime ends when the evaluation containing the full expression or full declarator ends, so the result of a function call can be accessed. This extension to the lifetime of a temporary also removes a quiet change to C90 and improves compatibility with C++.
+
+C functions may not return arrays; however, functions can return a pointer to an array or a `struct` or `union` that contains arrays. Consequently, in any version of C, if a function call returns by value a `struct` or `union` containing an array, do not modify those arrays within the expression containing the function call. In C99 and older, do not access an array returned by a function after the next sequence point or after the evaluation of the containing full expression or full declarator ends.
+
+## Noncompliant Code Example
+
+This noncompliant code example [conforms](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-conformingprogram) to the C11 Standard; however, it fails to conform to C99. If compiled with a C99-conforming implementation, this code has [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) because the sequence point preceding the call to `printf()` comes between the call and the access by `printf()` of the string in the returned object.
+
+```cpp
+#include
+
+struct X { char a[8]; };
+
+struct X salutation(void) {
+ struct X result = { "Hello" };
+ return result;
+}
+
+struct X addressee(void) {
+ struct X result = { "world" };
+ return result;
+}
+
+int main(void) {
+ printf("%s, %s!\n", salutation().a, addressee().a);
+ return 0;
+}
+
+```
+
+## Compliant Solution (C11 and newer)
+
+This compliant solution checks `__STDC_VERSION__` to ensure that a pre-C11 compiler will fail to compile the code, rather than invoking undefined behavior.
+
+```cpp
+#include
+
+#if __STDC_VERSION__ < 201112L
+#error This code requires a compiler supporting the C11 standard or newer
+#endif
+
+struct X { char a[8]; };
+
+struct X salutation(void) {
+ struct X result = { "Hello" };
+ return result;
+}
+
+struct X addressee(void) {
+ struct X result = { "world" };
+ return result;
+}
+
+int main(void) {
+ printf("%s, %s!\n", salutation().a, addressee().a);
+ return 0;
+}
+```
+
+## Compliant Solution
+
+This compliant solution stores the structures returned by the call to `addressee()` before calling the `printf()` function. Consequently, this program conforms to both C99 and C11.
+
+```cpp
+#include
+
+struct X { char a[8]; };
+
+struct X salutation(void) {
+ struct X result = { "Hello" };
+ return result;
+}
+
+struct X addressee(void) {
+ struct X result = { "world" };
+ return result;
+}
+
+int main(void) {
+ struct X my_salutation = salutation();
+ struct X my_addressee = addressee();
+
+ printf("%s, %s!\n", my_salutation.a, my_addressee.a);
+ return 0;
+}
+
+```
+
+## Noncompliant Code Example
+
+This noncompliant code example attempts to retrieve an array and increment the array's first value. The array is part of a `struct` that is returned by a function call. Consequently, the array has temporary lifetime, and modifying the array is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) in both C99 and C11.
+
+```cpp
+#include
+
+struct X { int a[6]; };
+
+struct X addressee(void) {
+ struct X result = { { 1, 2, 3, 4, 5, 6 } };
+ return result;
+}
+
+int main(void) {
+ printf("%x", ++(addressee().a[0]));
+ return 0;
+}
+
+```
+
+## Compliant Solution
+
+This compliant solution stores the structure returned by the call to `addressee()` as `my_x` before calling the `printf()` function. When the array is modified, its lifetime is no longer temporary but matches the lifetime of the block in `main()`.
+
+```cpp
+#include
+
+struct X { int a[6]; };
+
+struct X addressee(void) {
+ struct X result = { { 1, 2, 3, 4, 5, 6 } };
+ return result;
+}
+
+int main(void) {
+ struct X my_x = addressee();
+ printf("%x", ++(my_x.a[0]));
+ return 0;
+}
+
+```
+
+## Noncompliant Code Example
+
+This noncompliant code example attempts to save a pointer to an array that is part of a `struct` that is returned by a function call. Consequently, the array has temporary lifetime, and using the pointer to it outside of the full expression is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) in both C99 and C11.
+
+```cpp
+#include
+
+struct X { int a[6]; };
+
+struct X addressee(void) {
+ struct X result = { { 1, 2, 3, 4, 5, 6 } };
+ return result;
+}
+
+int main(void) {
+ int *my_a = addressee().a;
+ printf("%x", my_a[0]);
+ return 0;
+}
+
+```
+
+## Compliant Solution
+
+This compliant solution stores the structure returned by the call to `addressee()` as `my_x` before saving a pointer to its array member. When the pointer is used, its lifetime is no longer temporary but matches the lifetime of the block in `main()`.
+
+```cpp
+#include
+
+struct X { int a[6]; };
+
+struct X addressee(void) {
+ struct X result = { { 1, 2, 3, 4, 5, 6 } };
+ return result;
+}
+
+int main(void) {
+ struct X my_x = addressee();
+ int *my_a = my_x.a;
+ printf("%x", my_a[0]);
+ return 0;
+}
+
+```
+
+## Risk Assessment
+
+Attempting to modify an array or access it after its lifetime expires may result in erroneous program behavior.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP35-C | Low | Probable | Medium | P4 | L3 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | temporary-object-modification | Partially checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-EXP35 | |
Helix QAC | 2022.4 | C0450, C0455, C0459, C0464, C0465 C++3807, C++3808 | |
LDRA tool suite | 9.7.1 | 642 S, 42 D, 77 D | Enhanced Enforcement |
Parasoft C/C++test | 2022.2 | CERT_C-EXP35-a | Do not modify objects with temporary lifetime |
Polyspace Bug Finder | R2023a | CERT-C: Rule EXP35-C | Checks for accesses on objects with temporary lifetime (rule fully covered) |
PRQA QA-C | 9.7 | 0450 \[U\], 0455 \[U\], 0459 \[U\], 0464 \[U\], 0465 \[U\] | |
Splint | 3.1.1 | | |
RuleChecker | 22.04 | temporary-object-modification | Partially checked |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP35-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+ Taxonomy | Taxonomy item | Relationship |
ISO/IEC TR 24772:2013 | Dangling References to Stack Frames \[DCM\] | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TR 24772:2013 | Side-effects and Order of Evaluation \[SAM\] | Prior to 2018-01-12: CERT: Unspecified Relationship |
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+This implementation also always reports non-modifying accesses of objects with temporary lifetime, which are only compliant in C11.
+
+## References
+
+* CERT-C: [EXP35-C: Do not modify objects with temporary lifetime](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.ql b/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.ql
new file mode 100644
index 0000000000..3f7d9ae142
--- /dev/null
+++ b/c/cert/src/rules/EXP35-C/DoNotModifyObjectsWithTemporaryLifetime.ql
@@ -0,0 +1,30 @@
+/**
+ * @id c/cert/do-not-modify-objects-with-temporary-lifetime
+ * @name EXP35-C: Do not modify objects with temporary lifetime
+ * @description Attempting to modify an object with temporary lifetime results in undefined
+ * behavior.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/exp35-c
+ * correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.c.Objects
+
+// Note: Undefined behavior is possible regardless of whether the accessed field from the returned
+// struct is an array or a scalar (i.e. arithmetic and pointer types) member, according to the standard.
+from FieldAccess fa, TemporaryObjectIdentity tempObject
+where
+ not isExcluded(fa, InvalidMemory2Package::doNotModifyObjectsWithTemporaryLifetimeQuery()) and
+ fa.getQualifier().getUnconverted() = tempObject
+select fa, "Field access on $@ qualifier occurs after its temporary object lifetime.", tempObject,
+ "temporary object"
diff --git a/c/cert/src/rules/EXP36-C/DoNotCastPointerToMoreStrictlyAlignedPointerType.md b/c/cert/src/rules/EXP36-C/DoNotCastPointerToMoreStrictlyAlignedPointerType.md
new file mode 100644
index 0000000000..4a682f4afe
--- /dev/null
+++ b/c/cert/src/rules/EXP36-C/DoNotCastPointerToMoreStrictlyAlignedPointerType.md
@@ -0,0 +1,222 @@
+# EXP36-C: Do not cast pointers into more strictly aligned pointer types
+
+This query implements the CERT-C rule EXP36-C:
+
+> Do not cast pointers into more strictly aligned pointer types
+
+
+## Description
+
+Do not convert a pointer value to a pointer type that is more strictly aligned than the referenced type. Different alignments are possible for different types of objects. If the type-checking system is overridden by an explicit cast or the pointer is converted to a void pointer (`void *`) and then to a different type, the alignment of an object may be changed.
+
+The C Standard, 6.3.2.3, paragraph 7 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], states
+
+> A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.
+
+
+See [undefined behavior 25.](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_25)
+
+If the misaligned pointer is dereferenced, the program may [terminate abnormally](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-abnormaltermination). On some architectures, the cast alone may cause a loss of information even if the value is not dereferenced if the types involved have differing alignment requirements.
+
+## Noncompliant Code Example
+
+In this noncompliant example, the `char` pointer `&c` is converted to the more strictly aligned `int` pointer `ip`. On some [implementations](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation), `cp` will not match `&c`. As a result, if a pointer to one object type is converted to a pointer to a different object type, the second object type must not require stricter alignment than the first.
+
+```cpp
+#include
+
+void func(void) {
+ char c = 'x';
+ int *ip = (int *)&c; /* This can lose information */
+ char *cp = (char *)ip;
+
+ /* Will fail on some conforming implementations */
+ assert(cp == &c);
+}
+
+```
+
+## Compliant Solution (Intermediate Object)
+
+In this compliant solution, the `char` value is stored into an object of type `int` so that the pointer's value will be properly aligned:
+
+```cpp
+#include
+
+void func(void) {
+ char c = 'x';
+ int i = c;
+ int *ip = &i;
+
+ assert(ip == &i);
+}
+```
+
+## Noncompliant Code Example
+
+The C Standard allows any object pointer to be cast to and from `void *`. As a result, it is possible to silently convert from one pointer type to another without the compiler diagnosing the problem by storing or casting a pointer to `void *` and then storing or casting it to the final type. In this noncompliant code example, `loop_function()` is passed the `char` pointer `char_ptr` but returns an object of type `int` pointer:
+
+```cpp
+int *loop_function(void *v_pointer) {
+ /* ... */
+ return v_pointer;
+}
+
+void func(char *char_ptr) {
+ int *int_ptr = loop_function(char_ptr);
+
+ /* ... */
+}
+```
+This example compiles without warning using GCC 4.8 on Ubuntu Linux 14.04. However, `int_pointer` can be more strictly aligned than an object of type `char *`.
+
+## Compliant Solution
+
+Because the input parameter directly influences the return value, and `loop_function()` returns an object of type `int *`, the formal parameter `v_pointer` is redeclared to accept only an object of type `int *`:
+
+```cpp
+int *loop_function(int *v_pointer) {
+ /* ... */
+ return v_pointer;
+}
+
+void func(int *loop_ptr) {
+ int *int_ptr = loop_function(loop_ptr);
+
+ /* ... */
+}
+```
+
+## Noncompliant Code Example
+
+Some architectures require that pointers are correctly aligned when accessing objects larger than a byte. However, it is common in system code that unaligned data (for example, the network stacks) must be copied to a properly aligned memory location, such as in this noncompliant code example:
+
+```cpp
+#include
+
+struct foo_header {
+ int len;
+ /* ... */
+};
+
+void func(char *data, size_t offset) {
+ struct foo_header *tmp;
+ struct foo_header header;
+
+ tmp = (struct foo_header *)(data + offset);
+ memcpy(&header, tmp, sizeof(header));
+
+ /* ... */
+}
+```
+Assigning an unaligned value to a pointer that references a type that needs to be aligned is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). An [implementation](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) may notice, for example, that `tmp` and `header` must be aligned and use an inline `memcpy()` that uses instructions that assume aligned data.
+
+## Compliant Solution
+
+This compliant solution avoids the use of the `foo_header` pointer:
+
+```cpp
+#include
+
+struct foo_header {
+ int len;
+ /* ... */
+};
+
+void func(char *data, size_t offset) {
+ struct foo_header header;
+ memcpy(&header, data + offset, sizeof(header));
+
+ /* ... */
+}
+```
+
+## Exceptions
+
+**EXP36-C-EX1:** Some hardware architectures have relaxed requirements with regard to pointer alignment. Using a pointer that is not properly aligned is correctly handled by the architecture, although there might be a performance penalty. On such an architecture, improper pointer alignment is permitted but remains an efficiency problem.
+
+The x86 32- and 64-bit architectures usually impose only a performance penalty for violations of this rule, but under some circumstances, noncompliant code can still exhibit undefined behavior. Consider the following program:
+
+```cpp
+#include
+#include
+
+#define READ_UINT16(ptr) (*(uint16_t *)(ptr))
+#define WRITE_UINT16(ptr, val) (*(uint16_t *)(ptr) = (val))
+
+void compute(unsigned char *b1, unsigned char *b2,
+ int value, int range) {
+ int i;
+ for (i = 0; i < range; i++) {
+ int newval = (int)READ_UINT16(b1) + value;
+ WRITE_UINT16(b2, newval);
+ b1 += 2;
+ b2 += 2;
+ }
+}
+
+int main() {
+ unsigned char buffer1[1024];
+ unsigned char buffer2[1024];
+ printf("Compute something\n");
+ compute(buffer1 + 3, buffer2 + 1, 42, 500);
+ return 0;
+}
+```
+This code tries to read short ints (which are 16 bits long) from odd pairs in a character array, which violates this rule. On 32- and 64-bit x86 platforms, this program should run to completion without incident. However, the program aborts with a SIGSEGV due to the unaligned reads on a 64-bit platform running Debian Linux, when compiled with GCC 4.9.4 using the flags `-O3` or `-O2 -ftree-loop-vectorize -fvect-cost-model`.
+
+If a developer wishes to violate this rule and use undefined behavior, they must not only ensure that the hardware guarantees the behavior of the object code, but they must also ensure that their compiler, along with its optimizer, also respect these guarantees.
+
+**EXP36-C-EX2**: If a pointer is known to be correctly aligned to the target type, then a cast to that type is permitted. There are several cases where a pointer is known to be correctly aligned to the target type. The pointer could point to an object declared with a suitable alignment specifier. It could point to an object returned by `aligned_alloc()`, `calloc()`, `malloc()`, or `realloc()`, as per the C standard, section 7.22.3, paragraph 1 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\].
+
+This compliant solution uses the alignment specifier, which is new to C11, to declare the `char` object `c` with the same alignment as that of an object of type `int`. As a result, the two pointers reference equally aligned pointer types:
+
+```cpp
+#include
+#include
+
+void func(void) {
+ /* Align c to the alignment of an int */
+ alignas(int) char c = 'x';
+ int *ip = (int *)&c;
+ char *cp = (char *)ip;
+ /* Both cp and &c point to equally aligned objects */
+ assert(cp == &c);
+}
+```
+
+## Risk Assessment
+
+Accessing a pointer or an object that is not properly aligned can cause a program to crash or give erroneous information, or it can cause slow pointer accesses (if the architecture allows misaligned accesses).
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP36-C | Low | Probable | Medium | P4 | L3 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Astrée | 22.04 | pointer-cast-alignment | Fully checked |
Axivion Bauhaus Suite | 7.2.0 | CertC-EXP36 | |
CodeSonar | 7.2p0 | LANG.CAST.PC.OBJ | Cast: Object Pointers |
Compass/ROSE | | | Can detect violations of this rule. However, it does not flag explicit casts to void \* and then back to another pointer type |
Coverity | 2017.07 | MISRA C 2004 Rule 11.4 MISRA C 2012 Rule 11.1 MISRA C 2012 Rule 11.2 MISRA C 2012 Rule 11.5 MISRA C 2012 Rule 11.7 | Implemented |
ECLAIR | 1.2 | CC2.EXP36 | Fully implemented |
EDG | | | |
GCC | 4.3.5 | | Can detect some violations of this rule when the -Wcast-align flag is used |
Helix QAC | 2022.4 | C0326, C3305 C++3033, C++3038 | |
Klocwork | 2022.4 | MISRA.CAST.OBJ_PTR_TO_OBJ_PTR.2012 | |
LDRA tool suite | 9.7.1 | 94 S, 606 S | Partially implemented |
Parasoft C/C++test | 2022.2 | CERT_C-EXP36-a | A cast should not be performed between a pointer to object type and a different pointer to object type |
PC-lint Plus | 1.4 | 2445 | Partially supported: reports casts directly from a pointer to a less strictly aligned type to a pointer to a more strictly aligned type |
Polyspace Bug Finder | R2022b | CERT C: Rule EXP36-C | Checks for source buffer misaligned with destination buffer (rule fully covered) |
PRQA QA-C | 9.7 | 0326, 3305 | Fully implemented |
PRQA QA-C++ | 4.4 | 3033, 3038 | |
PVS-Studio | 7.22 | V548 , V641 , V1032 | |
RuleChecker | 22.04 | pointer-cast-alignment | Fully checked |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP36-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [EXP36-C: Do not cast pointers into more strictly aligned pointer types](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP36-C/DoNotCastPointerToMoreStrictlyAlignedPointerType.ql b/c/cert/src/rules/EXP36-C/DoNotCastPointerToMoreStrictlyAlignedPointerType.ql
new file mode 100644
index 0000000000..0d294e48b1
--- /dev/null
+++ b/c/cert/src/rules/EXP36-C/DoNotCastPointerToMoreStrictlyAlignedPointerType.ql
@@ -0,0 +1,188 @@
+/**
+ * @id c/cert/do-not-cast-pointer-to-more-strictly-aligned-pointer-type
+ * @name EXP36-C: Do not cast pointers into more strictly aligned pointer types
+ * @description Converting a pointer to a different type results in undefined behavior if the
+ * pointer is not correctly aligned for the new type.
+ * @kind path-problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/exp36-c
+ * correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.Alignment
+import semmle.code.cpp.dataflow.DataFlow
+import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
+import ExprWithAlignmentToCStyleCastFlow::PathGraph
+
+/**
+ * An expression with a type that has defined alignment requirements
+ */
+abstract class ExprWithAlignment extends Expr {
+ /**
+ * Gets the alignment requirements in bytes for the underlying `Expr`
+ */
+ abstract int getAlignment();
+
+ /**
+ * Gets a descriptive string describing the type of expression
+ */
+ abstract string getKind();
+}
+
+/**
+ * A class extending `AddressOfExpr` and `ExprWithAlignment` to reason about the
+ * alignment of base types addressed with C address-of expressions
+ */
+class AddressOfAlignedVariableExpr extends AddressOfExpr, ExprWithAlignment {
+ AddressOfAlignedVariableExpr() { this.getAddressable() instanceof Variable }
+
+ AlignAs getAlignAsAttribute() { result = this.getAddressable().(Variable).getAnAttribute() }
+
+ override int getAlignment() {
+ result = getAlignAsAttribute().getArgument(0).getValueInt()
+ or
+ result = getAlignAsAttribute().getArgument(0).getValueType().getSize()
+ or
+ not exists(getAlignAsAttribute()) and
+ result = this.getAddressable().(Variable).getType().getAlignment()
+ }
+
+ override string getKind() { result = "address-of expression" }
+}
+
+/**
+ * A class extending `FunctionCall` and `ExprWithAlignment` to reason about the
+ * alignment of pointers allocated with calls to C standard library allocation functions
+ */
+class DefinedAlignmentAllocationExpr extends FunctionCall, ExprWithAlignment {
+ int alignment;
+
+ DefinedAlignmentAllocationExpr() {
+ this.getTarget().getName() = "aligned_alloc" and
+ lowerBound(this.getArgument(0)) = upperBound(this.getArgument(0)) and
+ alignment = upperBound(this.getArgument(0))
+ or
+ this.getTarget().getName() = ["malloc", "calloc", "realloc"] and
+ alignment = getGlobalMaxAlignT()
+ }
+
+ override int getAlignment() { result = alignment }
+
+ override string getKind() { result = "call to " + this.getTarget().getName() }
+}
+
+/**
+ * An `Expr` of type `PointerType` but not `VoidPointerType`
+ * which is the unique non-`Conversion` expression of a `Cast`.
+ */
+class UnconvertedCastFromNonVoidPointerExpr extends Expr {
+ UnconvertedCastFromNonVoidPointerExpr() {
+ exists(CStyleCast cast |
+ cast.getUnconverted() = this and
+ this.getUnspecifiedType() instanceof PointerType and
+ not this.getUnspecifiedType() instanceof VoidPointerType
+ )
+ }
+}
+
+/**
+ * A class extending `UnconvertedCastFromNonVoidPointerExpr` and `ExprWithAlignment` to reason
+ * about the alignment of pointers accessed based solely on the pointers' base types.
+ */
+class DefaultAlignedPointerExpr extends UnconvertedCastFromNonVoidPointerExpr, ExprWithAlignment {
+ DefaultAlignedPointerExpr() {
+ not AllocationOrAddressOfExprToUnconvertedCastFromNonVoidPointerExprFlow::flowTo(DataFlow::exprNode(this))
+ }
+
+ override int getAlignment() { result = this.getType().(PointerType).getBaseType().getAlignment() }
+
+ override string getKind() {
+ result =
+ "pointer base type " +
+ this.getType().(PointerType).getBaseType().getUnspecifiedType().getName()
+ }
+}
+
+/**
+ * A data-flow configuration for tracking flow from `AddressOfExpr` which provide
+ * most reliable or explicitly defined alignment information to the less reliable
+ * `DefaultAlignedPointerAccessExpr` expressions.
+ *
+ * This data-flow configuration is used
+ * to exclude an `DefaultAlignedPointerAccessExpr` as a source if a preceding source
+ * defined by this configuration provides more accurate alignment information.
+ */
+module AllocationOrAddressOfExprToUnconvertedCastFromNonVoidPointerExprConfig implements
+ DataFlow::ConfigSig
+{
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr() instanceof AddressOfAlignedVariableExpr or
+ source.asExpr() instanceof DefinedAlignmentAllocationExpr
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() instanceof UnconvertedCastFromNonVoidPointerExpr
+ }
+}
+
+module AllocationOrAddressOfExprToUnconvertedCastFromNonVoidPointerExprFlow =
+ DataFlow::Global;
+
+/**
+ * A data-flow configuration for analysing the flow of `ExprWithAlignment` pointer expressions
+ * to casts which perform pointer type conversions and potentially create pointer alignment issues.
+ */
+module ExprWithAlignmentToCStyleCastConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) { source.asExpr() instanceof ExprWithAlignment }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(CStyleCast cast |
+ cast.getUnderlyingType() instanceof PointerType and
+ cast.getUnconverted() = sink.asExpr()
+ )
+ }
+
+ predicate isBarrierOut(DataFlow::Node node) {
+ // the default interprocedural data-flow model flows through any array assignment expressions
+ // to the qualifier (array base or pointer dereferenced) instead of the individual element
+ // that the assignment modifies. this default behaviour causes false positives for any future
+ // cast of the array base, so remove the assignment edge at the expense of false-negatives.
+ exists(AssignExpr a |
+ node.asExpr() = a.getRValue() and
+ (
+ a.getLValue() instanceof ArrayExpr or
+ a.getLValue() instanceof PointerDereferenceExpr
+ )
+ )
+ }
+}
+
+module ExprWithAlignmentToCStyleCastFlow = DataFlow::Global;
+
+from
+ ExprWithAlignmentToCStyleCastFlow::PathNode source,
+ ExprWithAlignmentToCStyleCastFlow::PathNode sink, ExprWithAlignment expr, CStyleCast cast,
+ Type toBaseType, int alignmentFrom, int alignmentTo
+where
+ not isExcluded(cast, Pointers3Package::doNotCastPointerToMoreStrictlyAlignedPointerTypeQuery()) and
+ ExprWithAlignmentToCStyleCastFlow::flowPath(source, sink) and
+ source.getNode().asExpr() = expr and
+ sink.getNode().asExpr() = cast.getUnconverted() and
+ toBaseType = cast.getActualType().(PointerType).getBaseType() and
+ alignmentTo = toBaseType.getAlignment() and
+ alignmentFrom = expr.getAlignment() and
+ // flag cases where the cast's target type has stricter alignment requirements than the source
+ alignmentFrom < alignmentTo
+select cast, source, sink,
+ "Cast from pointer with " + alignmentFrom +
+ "-byte alignment (defined by $@) to pointer with base type " + toBaseType.getUnderlyingType() +
+ " with " + alignmentTo + "-byte alignment.", expr.getUnconverted(), expr.getKind()
diff --git a/c/cert/src/rules/EXP37-C/CallPOSIXOpenWithCorrectArgumentCount.ql b/c/cert/src/rules/EXP37-C/CallPOSIXOpenWithCorrectArgumentCount.ql
index ad8520e321..a6e633d7f6 100644
--- a/c/cert/src/rules/EXP37-C/CallPOSIXOpenWithCorrectArgumentCount.ql
+++ b/c/cert/src/rules/EXP37-C/CallPOSIXOpenWithCorrectArgumentCount.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/exp37-c
* correctness
* security
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/EXP37-C/DoNotCallFunctionPointerWithIncompatibleType.ql b/c/cert/src/rules/EXP37-C/DoNotCallFunctionPointerWithIncompatibleType.ql
index 17e1c0e0e9..6d223dab72 100644
--- a/c/cert/src/rules/EXP37-C/DoNotCallFunctionPointerWithIncompatibleType.ql
+++ b/c/cert/src/rules/EXP37-C/DoNotCallFunctionPointerWithIncompatibleType.ql
@@ -8,13 +8,18 @@
* @problem.severity error
* @tags external/cert/id/exp37-c
* correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
import semmle.code.cpp.dataflow.DataFlow
-import DataFlow::PathGraph
+import SuspectFunctionPointerToCallFlow::PathGraph
/**
* An expression of type `FunctionPointer` which is the unconverted expression of a cast
@@ -37,26 +42,26 @@ class SuspiciousFunctionPointerCastExpr extends Expr {
* Data-flow configuration for flow from a `SuspiciousFunctionPointerCastExpr`
* to a call of the function pointer resulting from the function pointer cast
*/
-class SuspectFunctionPointerToCallConfig extends DataFlow::Configuration {
- SuspectFunctionPointerToCallConfig() { this = "SuspectFunctionPointerToCallConfig" }
-
- override predicate isSource(DataFlow::Node src) {
+module SuspectFunctionPointerToCallConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node src) {
src.asExpr() instanceof SuspiciousFunctionPointerCastExpr
}
- override predicate isSink(DataFlow::Node sink) {
+ predicate isSink(DataFlow::Node sink) {
exists(VariableCall call | sink.asExpr() = call.getExpr().(VariableAccess))
}
}
+module SuspectFunctionPointerToCallFlow = DataFlow::Global;
+
from
- SuspectFunctionPointerToCallConfig config, DataFlow::PathNode src, DataFlow::PathNode sink,
+ SuspectFunctionPointerToCallFlow::PathNode src, SuspectFunctionPointerToCallFlow::PathNode sink,
Access access
where
not isExcluded(src.getNode().asExpr(),
ExpressionsPackage::doNotCallFunctionPointerWithIncompatibleTypeQuery()) and
access = src.getNode().asExpr() and
- config.hasFlowPath(src, sink)
+ SuspectFunctionPointerToCallFlow::flowPath(src, sink)
select src, src, sink,
"Incompatible function $@ assigned to function pointer is eventually called through the pointer.",
access.getTarget(), access.getTarget().getName()
diff --git a/c/cert/src/rules/EXP37-C/DoNotCallFunctionsWithIncompatibleArguments.ql b/c/cert/src/rules/EXP37-C/DoNotCallFunctionsWithIncompatibleArguments.ql
index e76c62ee2d..4c5ba57504 100644
--- a/c/cert/src/rules/EXP37-C/DoNotCallFunctionsWithIncompatibleArguments.ql
+++ b/c/cert/src/rules/EXP37-C/DoNotCallFunctionsWithIncompatibleArguments.ql
@@ -8,6 +8,11 @@
* @problem.severity error
* @tags external/cert/id/exp37-c
* correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/EXP39-C/DoNotAccessVariableViaPointerOfIncompatibleType.md b/c/cert/src/rules/EXP39-C/DoNotAccessVariableViaPointerOfIncompatibleType.md
new file mode 100644
index 0000000000..cd01c4282e
--- /dev/null
+++ b/c/cert/src/rules/EXP39-C/DoNotAccessVariableViaPointerOfIncompatibleType.md
@@ -0,0 +1,312 @@
+# EXP39-C: Do not access a variable through a pointer of an incompatible type
+
+This query implements the CERT-C rule EXP39-C:
+
+> Do not access a variable through a pointer of an incompatible type
+
+
+## Description
+
+Modifying a variable through a pointer of an incompatible type (other than `unsigned char`) can lead to unpredictable results. Subclause 6.2.7 of the C Standard states that two types may be distinct yet compatible and addresses precisely when two distinct types are compatible.
+
+This problem is often caused by a violation of aliasing rules. The C Standard, 6.5, paragraph 7 \[ [ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011) \], specifies those circumstances in which an object may or may not be aliased.
+
+> An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
+
+
+* a type compatible with the effective type of the object,
+* a qualified version of a type compatible with the effective type of the object,
+* a type that is the signed or unsigned type corresponding to the effective type of the object,
+* a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
+* an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
+* a character type.
+Accessing an object by means of any other [lvalue](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-lvalue) expression (other than `unsigned char`) is [undefined behavior 37](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_37).
+
+## Noncompliant Code Example
+
+In this noncompliant example, an object of type `float` is incremented through an `int *`. The programmer can use the unit in the last place to get the next representable value for a floating-point type. However, accessing an object through a pointer of an incompatible type is undefined behavior.
+
+```cpp
+#include
+
+void f(void) {
+ if (sizeof(int) == sizeof(float)) {
+ float f = 0.0f;
+ int *ip = (int *)&f;
+ (*ip)++;
+ printf("float is %f\n", f);
+ }
+}
+
+```
+
+## Compliant Solution
+
+In this compliant solution, the standard C function `nextafterf()` is used to round toward the highest representable floating-point value:
+
+```cpp
+#include
+#include
+#include
+
+void f(void) {
+ float f = 0.0f;
+ f = nextafterf(f, FLT_MAX);
+ printf("float is %f\n", f);
+}
+
+```
+
+## Noncompliant Code Example
+
+In this noncompliant code example, an array of two values of type `short` is treated as an integer and assigned an integer value. The resulting values are indeterminate.
+
+```cpp
+#include
+
+void func(void) {
+ short a[2];
+ a[0]=0x1111;
+ a[1]=0x1111;
+
+ *(int *)a = 0x22222222;
+
+ printf("%x %x\n", a[0], a[1]);
+}
+```
+When translating this code, an implementation can assume that no access through an integer pointer can change the array `a`, consisting of shorts. Consequently, `printf()` may be called with the original values of `a[0]` and `a[1]`.
+
+**Implementation Details**
+
+Recent versions of GCC turn on the option `-fstrict-aliasing,` which allows alias-based optimizations, by default with `-O2`. Some architectures then print "1111 1111" as a result. Without optimization, the executable generates the *expected* output "2222 2222."
+
+To disable optimizations based on alias analysis for faulty legacy code, the option `-fno-strict-aliasing` can be used as a workaround. The option `-Wstrict-aliasing,` which is included in `-Wall,` warns about some, but not all, violations of aliasing rules when `-fstrict-aliasing` is active.
+
+When GCC 3.4.6 compiles this code with optimization, the assignment through the aliased pointer is effectively eliminated.
+
+## Compliant Solution
+
+This compliant solution uses a `union` type that includes a type compatible with the effective type of the object:
+
+```cpp
+#include
+
+void func(void) {
+ union {
+ short a[2];
+ int i;
+ } u;
+
+ u.a[0]=0x1111;
+ u.a[1]=0x1111;
+ u.i = 0x22222222;
+
+ printf("%x %x\n", u.a[0], u.a[1]);
+
+ /* ... */
+}
+```
+The C standard states:
+
+> If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called “type punning”). This might be a trap representation.
+
+
+The call to `printf()` typically outputs "2222 2222". However, there is no guarantee that this will be true; the object representations of `a` and `i` are unspecified and need not be compatible in this way, despite this operation being commonly accepted as an implementation extension. (See [unspecified behavior 11](https://wiki.sei.cmu.edu/confluence/display/c/DD.+Unspecified+Behavior#DD.UnspecifiedBehavior-unspecifiedbehavior11).)
+
+## Noncompliant Code Example
+
+In this noncompliant code example, a `gadget` object is allocated, then `realloc()` is called to create a `widget` object using the memory from the `gadget` object. Although reusing memory to change types is acceptable, accessing the memory copied from the original object is undefined behavior.
+
+```cpp
+#include
+
+struct gadget {
+ int i;
+ double d;
+ char *p;
+};
+
+struct widget {
+ char *q;
+ int j;
+ double e;
+};
+
+void func(void) {
+ struct gadget *gp;
+ struct widget *wp;
+
+ gp = (struct gadget *)malloc(sizeof(struct gadget));
+ if (!gp) {
+ /* Handle error */
+ }
+ /* ... Initialize gadget ... */
+ wp = (struct widget *)realloc(gp, sizeof(struct widget));
+ if (!wp) {
+ free(gp);
+ /* Handle error */
+ }
+ if (wp->j == 12) {
+ /* ... */
+ }
+ /* ... */
+ free(wp);
+}
+```
+
+## Compliant Solution
+
+This compliant solution reuses the memory from the `gadget` object but reinitializes the memory to a consistent state before reading from it:
+
+```cpp
+#include
+#include
+
+struct gadget {
+ int i;
+ double d;
+ char *p;
+};
+
+struct widget {
+ char *q;
+ int j;
+ double e;
+};
+
+void func(void) {
+ struct gadget *gp;
+ struct widget *wp;
+
+ gp = (struct gadget *)malloc(sizeof (struct gadget));
+ if (!gp) {
+ /* Handle error */
+ }
+ /* ... */
+ wp = (struct widget *)realloc(gp, sizeof(struct widget));
+ if (!wp) {
+ free(gp);
+ /* Handle error */
+ }
+ memset(wp, 0, sizeof(struct widget));
+ /* ... Initialize widget ... */
+
+ if (wp->j == 12) {
+ /* ... */
+ }
+ /* ... */
+ free(wp);
+}
+```
+
+## Noncompliant Code Example
+
+According to the C Standard, 6.7.6.2 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], using two or more incompatible arrays in an expression is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). (See also [undefined behavior 76](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_76).)
+
+For two array types to be compatible, both should have compatible underlying element types, and both size specifiers should have the same constant value. If either of these properties is violated, the resulting behavior is undefined.
+
+In this noncompliant code example, the two arrays `a` and `b` fail to satisfy the equal size specifier criterion for array compatibility. Because `a` and `b` are not equal, writing to what is believed to be a valid member of `a` might exceed its defined memory boundary, resulting in an arbitrary memory overwrite.
+
+```cpp
+enum { ROWS = 10, COLS = 15 };
+
+void func(void) {
+ int a[ROWS][COLS];
+ int (*b)[ROWS] = a;
+}
+```
+Most compilers will produce a warning diagnostic if the two array types used in an expression are incompatible.
+
+## Compliant Solution
+
+In this compliant solution, `b` is declared to point to an array with the same number of elements as `a`, satisfying the size specifier criterion for array compatibility:
+
+```cpp
+enum { ROWS = 10, COLS = 15 };
+
+void func(void) {
+ int a[ROWS][COLS];
+ int (*b)[COLS] = a;
+}
+```
+
+## Risk Assessment
+
+Optimizing for performance can lead to aliasing errors that can be quite difficult to detect. Furthermore, as in the preceding example, unexpected results can lead to buffer overflow attacks, bypassing security checks, or unexpected execution.
+
+ Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP39-C | Medium | Unlikely | High | P2 | L3 |
+
+
+## Automated Detection
+
+ Tool | Version | Checker | Description |
Helix QAC | 2022.4 | C0310, C0751, C3305 C++3017, C++3030, C++3033 | |
Klocwork | 2022.4 | MISRA.CAST.FUNC_PTR.2012 MISRA.CAST.INCOMPLETE_PTR_TO_ANY.2012 MISRA.CAST.OBJ_PTR_TO_NON_INT.2012 MISRA.CAST.OBJ_PTR_TO_OBJ_PTR.2012 | |
LDRA tool suite | 9.7.1 | 94 S, 554 S | Partially implemented |
Parasoft C/C++test | 2022.2 | CERT_C-EXP39-a CERT_C-EXP39-b CERT_C-EXP39-c CERT_C-EXP39-d CERT_C-EXP39-e CERT_C-EXP39-f | There shall be no implicit conversions from integral to floating type A cast should not be performed between a pointer to object type and a different pointer to object type Avoid accessing arrays and pointers out of bounds Avoid buffer overflow from tainted data due to defining incorrect format limits Avoid buffer read overflow from tainted data Avoid buffer write overflow from tainted data |
Polyspace Bug Finder | R2022b | CERT C: Rule EXP39-C | Checks for cast to pointer pointing to object of different type (rule partially covered) |
PRQA QA-C | 9.7 | 0310, 0751, 3305 | Partially implemented |
PRQA QA-C++ | 4.4 | 3017, 3030, 3033 | |
PVS-Studio | 7.22 | V580 | |
+
+
+## Related Vulnerabilities
+
+Search for vulnerabilities resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP39-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+ Taxonomy | Taxonomy item | Relationship |
ISO/IEC TS 17961 | Accessing an object through a pointer to an incompatible type \[ptrcomp\] | Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2.11 | CWE-119 , Improper Restriction of Operations within the Bounds of a Memory Buffer | 2017-05-18: CERT: Partial overlap |
CWE 2.11 | CWE-125 , Out-of-bounds Read | 2017-05-18: CERT: Partial overlap |
CWE 2.11 | CWE-704 | 2017-06-14: CERT: Rule subset of CWE |
+
+
+## CERT-CWE Mapping Notes
+
+[Key here](https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152408#HowthisCodingStandardisOrganized-CERT-CWEMappingNotes) for mapping notes
+
+**CWE-119 and EXP39-C**
+
+Independent( ARR30-C, ARR38-C, ARR32-C, INT30-C, INT31-C, EXP39-C, EXP33-C, FIO37-C) STR31-C = Subset( Union( ARR30-C, ARR38-C)) STR32-C = Subset( ARR38-C)
+
+Intersection( EXP39-C, CWE-119) =
+
+* Reading memory assigned to one type, but being accessed through a pointer to a larger type.
+EXP39-C – CWE-119 =
+* Writing to memory assigned to one type, but accessed through a pointer to a larger type
+* Reading memory assigned to one type, but being accessed through a pointer to a smaller (or equal-sized) type
+CWE-119 – EXP39-C =
+* Reading beyond a buffer using a means other than accessing a variable through an incompatible pointer.
+**CWE-123 and EXP39-C**
+
+Intersection( CWE-123, EXP39-C) = Ø
+
+EXP39-C allows overflowing a (small) buffer, but not arbitrary memory writes. (Possibly an arbitrary-memory write exploit could be devised using a “perfect storm” of incompatible types, but this would be uncommon in practice.)
+
+**CWE-125 and EXP39-C**
+
+Independent( ARR30-C, ARR38-C, EXP39-C, INT30-C) STR31-C = Subset( Union( ARR30-C, ARR38-C)) STR32-C = Subset( ARR38-C)
+
+Intersection( EXP39-C, CWE-125) =
+
+* Reading memory assigned to one type, but being accessed through a pointer to a larger type.
+ESP39-C – CWE-125 =
+* Reading memory assigned to one type, but being accessed through a pointer to a smaller (or equal-sized) type
+CWE-125 – EXP39-C =
+* Reading beyond a buffer using a means other than accessing a variable through an incompatible pointer.
+**CWE-188 and EXP39-C**
+
+Intersection( CWE-188, EXP39-C) = Ø
+
+CWE-188 appears to be about making assumptions about the layout of memory between distinct variables (that are not part of a larger struct or array). Such assumptions typically involve pointer arithmetic (which violates ARR30-C). EXP39-C involves only one object in memory being (incorrectly) interpreted as if it were another object. EG a float being treated as an int (usually via pointers and typecasting)
+
+**CWE-704 and EXP39-C**
+
+CWE-704 = Union( EXP39-C, list) where list =
+
+* Incorrect (?) typecast that is not incompatible
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [EXP39-C: Do not access a variable through a pointer of an incompatible type](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP39-C/DoNotAccessVariableViaPointerOfIncompatibleType.ql b/c/cert/src/rules/EXP39-C/DoNotAccessVariableViaPointerOfIncompatibleType.ql
new file mode 100644
index 0000000000..856cad1d58
--- /dev/null
+++ b/c/cert/src/rules/EXP39-C/DoNotAccessVariableViaPointerOfIncompatibleType.ql
@@ -0,0 +1,209 @@
+/**
+ * @id c/cert/do-not-access-variable-via-pointer-of-incompatible-type
+ * @name EXP39-C: Do not access a variable through a pointer of an incompatible type
+ * @description Modifying underlying pointer data through a pointer of an incompatible type can lead
+ * to unpredictable results.
+ * @kind path-problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/exp39-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p2
+ * external/cert/level/l3
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import semmle.code.cpp.dataflow.DataFlow
+import semmle.code.cpp.controlflow.Dominance
+import IndirectCastFlow::PathGraph
+
+/**
+ * The standard function `memset` and its assorted variants
+ */
+class MemsetFunction extends Function {
+ MemsetFunction() {
+ this.hasGlobalOrStdOrBslName("memset")
+ or
+ this.hasGlobalOrStdName("wmemset")
+ or
+ this.hasGlobalName(["__builtin_memset", "__builtin___memset_chk", "__builtin_memset_chk"])
+ }
+}
+
+class IndirectCastAnalysisUnconvertedCastExpr extends Expr {
+ IndirectCastAnalysisUnconvertedCastExpr() { this = any(Cast c).getUnconverted() }
+}
+
+class IndirectCastAnalysisDereferenceSink extends Expr {
+ IndirectCastAnalysisDereferenceSink() { dereferenced(this) }
+}
+
+class ReallocationFunction extends AllocationFunction {
+ ReallocationFunction() { exists(this.getReallocPtrArg()) }
+}
+
+newtype IndirectCastFlowState =
+ /**
+ * A data-flow state for a pointer which has not been reallocated.
+ */
+ IndirectCastDefaultFlowState() or
+ /**
+ * A data-flow state for a pointer which has been reallocated but
+ * has not yet been zeroed with a memset call.
+ */
+ IndirectCastReallocatedFlowState()
+
+/**
+ * A data-flow configuration to track the flow from cast expressions to either
+ * other cast expressions or to dereferences of pointers reallocated with a call
+ * to `realloc` but not cleared via a function call to `memset`.
+ */
+module IndirectCastConfig implements DataFlow::StateConfigSig {
+ class FlowState = IndirectCastFlowState;
+
+ predicate isSource(DataFlow::Node source, FlowState state) {
+ state instanceof IndirectCastDefaultFlowState and
+ source.asExpr() instanceof IndirectCastAnalysisUnconvertedCastExpr
+ }
+
+ predicate isSink(DataFlow::Node sink, FlowState state) {
+ sink.asExpr() instanceof IndirectCastAnalysisUnconvertedCastExpr and
+ state instanceof IndirectCastDefaultFlowState
+ or
+ sink.asExpr() instanceof IndirectCastAnalysisDereferenceSink and
+ state instanceof IndirectCastReallocatedFlowState and
+ // The memset call won't always have an edge to subsequent dereferences.
+ //
+ // Therefore, check that:
+ // 1) The memset call dominates the dereference.
+ // 2) The realloc call dominates the memset call.
+ // 3) There is no subsequent memset that also dominates the dereference.
+ //
+ // Currently, there is no relation between the pointer passed to memset
+ // and the pointer dereferenced. This unimplemented check might produce
+ // false-negatives when the memset call is unrelated to the reallocated memory.
+ not exists(FunctionCall memset, FunctionCall realloc, Expr ptr |
+ memset.getTarget() instanceof MemsetFunction and
+ realloc.getTarget() instanceof ReallocationFunction and
+ ptr = sink.asExpr() and
+ dominates(memset, ptr) and
+ not dominates(memset,
+ any(FunctionCall other |
+ other.getTarget() instanceof MemsetFunction and
+ other != memset and
+ dominates(other, ptr)
+ |
+ other
+ )) and
+ dominates(realloc, memset)
+ )
+ }
+
+ predicate isBarrier(DataFlow::Node node, FlowState state) {
+ state instanceof IndirectCastReallocatedFlowState and
+ exists(FunctionCall fc |
+ fc.getTarget() instanceof MemsetFunction and
+ fc.getArgument(0) = node.asExpr()
+ )
+ }
+
+ predicate isAdditionalFlowStep(
+ DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
+ ) {
+ // track pointer flow through realloc calls and update state to `IndirectCastReallocatedFlowState`
+ state1 instanceof IndirectCastDefaultFlowState and
+ state2 instanceof IndirectCastReallocatedFlowState and
+ exists(FunctionCall fc |
+ fc.getTarget() instanceof ReallocationFunction and
+ node1.asExpr() = fc.getArgument(fc.getTarget().(ReallocationFunction).getReallocPtrArg()) and
+ node2.asExpr() = fc
+ )
+ or
+ // track pointer flow through memset calls and reset state to `IndirectCastDefaultFlowState`
+ state1 instanceof IndirectCastReallocatedFlowState and
+ state2 instanceof IndirectCastDefaultFlowState and
+ exists(FunctionCall fc |
+ fc.getTarget() instanceof MemsetFunction and
+ node1.asExpr() = fc.getArgument(0) and
+ node2.asExpr() = fc
+ )
+ }
+}
+
+module IndirectCastFlow = DataFlow::GlobalWithState;
+
+pragma[inline]
+predicate areTypesSameExceptForConstSpecifiers(Type a, Type b) {
+ a.stripType() = b.stripType() and
+ a.getSize() = b.getSize() and
+ forall(Specifier s | s = a.getASpecifier() and not s.hasName("const") |
+ b.hasSpecifier(s.getName())
+ )
+}
+
+pragma[inline]
+Type compatibleTypes(Type type) {
+ not (
+ type.isVolatile() and not result.isVolatile()
+ or
+ type.isConst() and not result.isConst()
+ ) and
+ (
+ (
+ // all types are compatible with void and explicitly-unsigned char types
+ result instanceof UnsignedCharType or
+ [result.stripTopLevelSpecifiers(), type.stripTopLevelSpecifiers()] instanceof VoidType
+ )
+ or
+ not result instanceof UnsignedCharType and
+ not result instanceof VoidType and
+ (
+ type.stripType() instanceof Struct and
+ type.getUnspecifiedType() = result.getUnspecifiedType() and
+ not type.(Struct).isAnonymous() and
+ not result.(Struct).isAnonymous()
+ or
+ not type.stripType() instanceof Struct and
+ (
+ areTypesSameExceptForConstSpecifiers(type, result)
+ or
+ result.getSize() = type.getSize() and
+ (
+ type instanceof Enum and result instanceof IntegralOrEnumType
+ or
+ not type instanceof PlainCharType and
+ (
+ result.(IntegralType).isSigned() and type.(IntegralType).isSigned()
+ or
+ result.(IntegralType).isUnsigned() and type.(IntegralType).isUnsigned()
+ )
+ or
+ result.(FloatingPointType).getDomain() = type.(FloatingPointType).getDomain()
+ )
+ or
+ type instanceof Enum and result instanceof IntegralOrEnumType
+ )
+ )
+ )
+}
+
+from
+ IndirectCastFlow::PathNode source, IndirectCastFlow::PathNode sink, Cast cast, Type fromType,
+ Type toType
+where
+ not isExcluded(sink.getNode().asExpr(),
+ Pointers3Package::doNotAccessVariableViaPointerOfIncompatibleTypeQuery()) and
+ cast.getFile().compiledAsC() and
+ IndirectCastFlow::flowPath(source, sink) and
+ // include only sinks which are not a compatible type to the associated source
+ source.getNode().asExpr() = cast.getUnconverted() and
+ fromType = cast.getUnconverted().getType().(PointerType).getBaseType() and
+ toType = sink.getNode().asExpr().getActualType().(PointerType).getBaseType() and
+ not toType = compatibleTypes(fromType)
+select sink.getNode().asExpr().getUnconverted(), source, sink,
+ "Cast from " + fromType + " to " + toType + " results in an incompatible pointer base type."
diff --git a/c/cert/src/rules/EXP40-C/DoNotModifyConstantObjects.md b/c/cert/src/rules/EXP40-C/DoNotModifyConstantObjects.md
new file mode 100644
index 0000000000..6effa8e7c4
--- /dev/null
+++ b/c/cert/src/rules/EXP40-C/DoNotModifyConstantObjects.md
@@ -0,0 +1,95 @@
+# EXP40-C: Do not modify constant objects
+
+This query implements the CERT-C rule EXP40-C:
+
+> Do not modify constant objects
+
+
+## Description
+
+The C Standard, 6.7.3, paragraph 6 \[[IS](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)[O/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], states
+
+> If an attempt is made to modify an object defined with a `const`-qualified type through use of an [lvalue](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-lvalue) with non-`const`-qualified type, the behavior is undefined.
+
+
+See also [undefined behavior 64](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_64).
+
+There are existing compiler [implementations](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) that allow `const`-qualified objects to be modified without generating a warning message.
+
+Avoid casting away `const` qualification because doing so makes it possible to modify `const`-qualified objects without issuing diagnostics. (See [EXP05-C. Do not cast away a const qualification](https://wiki.sei.cmu.edu/confluence/display/c/EXP05-C.+Do+not+cast+away+a+const+qualification) and [STR30-C. Do not attempt to modify string literals](https://wiki.sei.cmu.edu/confluence/display/c/STR30-C.+Do+not+attempt+to+modify+string+literals) for more details.)
+
+## Noncompliant Code Example
+
+This noncompliant code example allows a constant object to be modified:
+
+```cpp
+const int **ipp;
+int *ip;
+const int i = 42;
+
+void func(void) {
+ ipp = &ip; /* Constraint violation */
+ *ipp = &i; /* Valid */
+ *ip = 0; /* Modifies constant i (was 42) */
+}
+```
+The first assignment is unsafe because it allows the code that follows it to attempt to change the value of the `const` object `i`.
+
+**Implementation Details**
+
+If `ipp`, `ip`, and `i` are declared as automatic variables, this example compiles without warning with Microsoft Visual Studio 2013 when compiled in C mode (`/TC`) and the resulting program changes the value of `i`. GCC 4.8.1 generates a warning but compiles, and the resulting program changes the value of `i`.
+
+If `ipp`, `ip`, and `i` are declared with static storage duration, this program compiles without warning and terminates abnormally with Microsoft Visual Studio 2013, and compiles with warning and terminates abnormally with GCC 4.8.1.
+
+## Compliant Solution
+
+The compliant solution depends on the intent of the programmer. If the intent is that the value of `i` is modifiable, then it should not be declared as a constant, as in this compliant solution:
+
+```cpp
+int **ipp;
+int *ip;
+int i = 42;
+
+void func(void) {
+ ipp = &ip; /* Valid */
+ *ipp = &i; /* Valid */
+ *ip = 0; /* Valid */
+}
+```
+If the intent is that the value of i is not meant to change, then do not write noncompliant code that attempts to modify it.
+
+## Risk Assessment
+
+Modifying constant objects through nonconstant references is [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior).
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP40-C | Low | Unlikely | Medium | P2 | L3 |
+
+
+## Automated Detection
+
+
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP40-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+The implementation does not consider pointer aliasing via multiple indirection.
+
+## References
+
+* CERT-C: [EXP40-C: Do not modify constant objects](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP40-C/DoNotModifyConstantObjects.ql b/c/cert/src/rules/EXP40-C/DoNotModifyConstantObjects.ql
new file mode 100644
index 0000000000..9d8e4b16d4
--- /dev/null
+++ b/c/cert/src/rules/EXP40-C/DoNotModifyConstantObjects.ql
@@ -0,0 +1,66 @@
+/**
+ * @id c/cert/do-not-modify-constant-objects
+ * @name EXP40-C: Do not modify constant objects
+ * @description Do not modify constant objects. This may result in undefined behavior.
+ * @kind path-problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/exp40-c
+ * correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p2
+ * external/cert/level/l3
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import semmle.code.cpp.dataflow.DataFlow
+import CastFlow::PathGraph
+import codingstandards.cpp.SideEffect
+
+class ConstRemovingCast extends Cast {
+ ConstRemovingCast() {
+ this.getExpr().getType().(DerivedType).getBaseType*().isConst() and
+ not this.getType().(DerivedType).getBaseType*().isConst()
+ }
+}
+
+class MaybeReturnsStringLiteralFunctionCall extends FunctionCall {
+ MaybeReturnsStringLiteralFunctionCall() {
+ getTarget().getName() in [
+ "strpbrk", "strchr", "strrchr", "strstr", "wcspbrk", "wcschr", "wcsrchr", "wcsstr",
+ "memchr", "wmemchr"
+ ]
+ }
+}
+
+module CastConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr().getFullyConverted() instanceof ConstRemovingCast
+ or
+ source.asExpr().getFullyConverted() = any(MaybeReturnsStringLiteralFunctionCall c)
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() = any(Assignment a).getLValue().(PointerDereferenceExpr).getOperand()
+ }
+}
+
+module CastFlow = DataFlow::Global;
+
+from CastFlow::PathNode src, CastFlow::PathNode sink
+where
+ CastFlow::flowPath(src, sink)
+ or
+ sink.getNode()
+ .asExpr()
+ .(VariableEffect)
+ .getTarget()
+ .getType()
+ .(DerivedType)
+ .getBaseType*()
+ .isConst()
+select sink, src, sink, "Const variable assigned with non const-value."
diff --git a/c/cert/src/rules/EXP42-C/DoNotComparePaddingData.md b/c/cert/src/rules/EXP42-C/DoNotComparePaddingData.md
new file mode 100644
index 0000000000..297b718852
--- /dev/null
+++ b/c/cert/src/rules/EXP42-C/DoNotComparePaddingData.md
@@ -0,0 +1,134 @@
+# EXP42-C: Do not compare padding data
+
+This query implements the CERT-C rule EXP42-C:
+
+> Do not compare padding data
+
+
+## Description
+
+The C Standard, 6.7.2.1 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], states
+
+> There may be unnamed padding within a structure object, but not at its beginning. . . . There may be unnamed padding at the end of a structure or union.
+
+
+Subclause 6.7.9, paragraph 9, states that
+
+> unnamed members of objects of structure and union type do not participate in initialization. Unnamed members of structure objects have indeterminate value even after initialization.
+
+
+The only exception is that padding bits are set to zero when a static or thread-local object is implicitly initialized (paragraph10):
+
+> If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, then:
+
+
+— if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
+
+— if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
+
+Because these padding values are unspecified, attempting a byte-by-byte comparison between structures can lead to incorrect results \[[Summit 1995](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-Summit95)\].
+
+## Noncompliant Code Example
+
+In this noncompliant code example, `memcmp()` is used to compare the contents of two structures, including any padding bytes:
+
+```cpp
+#include
+
+struct s {
+ char c;
+ int i;
+ char buffer[13];
+};
+
+void compare(const struct s *left, const struct s *right) {
+ if ((left && right) &&
+ (0 == memcmp(left, right, sizeof(struct s)))) {
+ /* ... */
+ }
+}
+```
+
+## Compliant Solution
+
+In this compliant solution, all of the fields are compared manually to avoid comparing any padding bytes:
+
+```cpp
+#include
+
+struct s {
+ char c;
+ int i;
+ char buffer[13];
+};
+
+void compare(const struct s *left, const struct s *right) {
+ if ((left && right) &&
+ (left->c == right->c) &&
+ (left->i == right->i) &&
+ (0 == memcmp(left->buffer, right->buffer, 13))) {
+ /* ... */
+ }
+}
+```
+
+## Exceptions
+
+**EXP42-C-EX1**: A structure can be defined such that the members are aligned properly or the structure is packed using implementation-specific packing instructions. This is true only when the members' data types have no padding bits of their own and when their object representations are the same as their value representations. This frequently is not true for the `_Bool` type or floating-point types and need not be true for pointers. In such cases, the compiler does not insert padding, and use of functions such as `memcmp()` is acceptable.
+
+This compliant example uses the [\#pragma pack](http://msdn.microsoft.com/en-us/library/2e70t5y1.aspx) compiler extension from Microsoft Visual Studio to ensure the structure members are packed as tightly as possible:
+
+```cpp
+#include
+
+#pragma pack(push, 1)
+struct s {
+ char c;
+ int i;
+ char buffer[13];
+};
+#pragma pack(pop)
+
+void compare(const struct s *left, const struct s *right) {
+ if ((left && right) &&
+ (0 == memcmp(left, right, sizeof(struct s)))) {
+ /* ... */
+ }
+}
+```
+
+## Risk Assessment
+
+Comparing padding bytes, when present, can lead to [unexpected program behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-unexpectedbehavior).
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP42-C | Medium | Probable | Medium | P8 | L2 |
+
+
+## Automated Detection
+
+
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP42-C).
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [EXP42-C: Do not compare padding data](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP42-C/DoNotComparePaddingData.ql b/c/cert/src/rules/EXP42-C/DoNotComparePaddingData.ql
new file mode 100644
index 0000000000..4fb80352a3
--- /dev/null
+++ b/c/cert/src/rules/EXP42-C/DoNotComparePaddingData.ql
@@ -0,0 +1,24 @@
+/**
+ * @id c/cert/do-not-compare-padding-data
+ * @name EXP42-C: Do not compare padding data
+ * @description Padding data values are unspecified and should not be included in comparisons.
+ * @kind problem
+ * @precision very-high
+ * @problem.severity error
+ * @tags external/cert/id/exp42-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p8
+ * external/cert/level/l2
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.rules.memcmpusedtocomparepaddingdata.MemcmpUsedToComparePaddingData
+
+class DoNotComparePaddingDataQuery extends MemcmpUsedToComparePaddingDataSharedQuery {
+ DoNotComparePaddingDataQuery() { this = Memory2Package::doNotComparePaddingDataQuery() }
+}
diff --git a/c/cert/src/rules/EXP43-C/DoNotPassAliasedPointerToRestrictQualifiedParam.md b/c/cert/src/rules/EXP43-C/DoNotPassAliasedPointerToRestrictQualifiedParam.md
new file mode 100644
index 0000000000..855f7ce963
--- /dev/null
+++ b/c/cert/src/rules/EXP43-C/DoNotPassAliasedPointerToRestrictQualifiedParam.md
@@ -0,0 +1,296 @@
+# EXP43-C: Do not pass aliased pointers to restrict-qualified parameters
+
+This query implements the CERT-C rule EXP43-C:
+
+> Avoid undefined behavior when using restrict-qualified pointers
+
+
+## Description
+
+An object that is accessed through a `restrict`-qualified pointer has a special association with that pointer. This association requires that all accesses to that object use, directly or indirectly, the value of that particular pointer. The intended use of the restrict qualifier is to promote optimization, and deleting all instances of the qualifier from a program does not change its meaning (that is, observable behavior). In the absence of this qualifier, other pointers can alias this object. Caching the value in an object designated through a `restrict`-qualified pointer is safe at the beginning of the block in which the pointer is declared because no preexisting aliases may also be used to reference that object. The cached value must be restored to the object by the end of the block, where preexisting aliases again become available. New aliases may be formed within the block, but these must all depend on the value of the `restrict`-qualified pointer so that they can be identified and adjusted to refer to the cached value. For a `restrict`-qualified pointer at file scope, the block is the body of each function in the file \[[Walls 2006](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography)\]. Developers should be aware that C++ does not support the `restrict` qualifier, but some C++ compiler implementations support an equivalent qualifier as an extension.
+
+The C Standard \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\] identifies the following [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior):
+
+> A restrict-qualified pointer is assigned a value based on another restricted pointer whose associated block neither began execution before the block associated with this pointer, nor ended before the assignment (6.7.3.1).
+
+
+This is an oversimplification, however, and it is important to review the formal definition of *restrict* in subclause 6.7.3.1 of the C Standard to properly understand undefined behaviors associated with the use of `restrict`-qualified pointers.
+
+## Overlapping Objects
+
+The `restrict` qualifier requires that the pointers do not reference overlapping objects. If the objects referenced by arguments to functions overlap (meaning the objects share some common memory addresses), the behavior is undefined.
+
+**Noncompliant Code Example**
+
+This code example is noncompliant because an assignment is made between two `restrict`-qualified pointers in the same scope:
+
+```cpp
+int *restrict a;
+int *restrict b;
+
+extern int c[];
+
+int main(void) {
+ c[0] = 17;
+ c[1] = 18;
+ a = &c[0];
+ b = &c[1];
+ a = b; /* Undefined behavior */
+ /* ... */
+}
+```
+Note that [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) occurs only when `a` is assigned to `b`. It is valid for `a` and `b` to point into the same array object, provided the range of elements accessed through one of the pointers does not overlap with the range of elements accessed through the other pointer.
+
+**Compliant Solution**
+
+One way to eliminate the [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) is simply to remove the `restrict-`qualification from the affected pointers:
+
+```cpp
+int *a;
+int *b;
+
+extern int c[];
+
+int main(void) {
+ c[0] = 17;
+ c[1] = 18;
+ a = &c[0];
+ b = &c[1];
+ a = b; /* Defined behavior */
+ /* ... */
+}
+```
+
+## restrict-Qualified Function Parameters
+
+When calling functions that have `restrict`-qualified function parameters, it is important that the pointer arguments do not reference overlapping objects if one or more of the pointers are used to modify memory. Consequently, it is important to understand the semantics of the function being called.
+
+**Noncompliant Code Example**
+
+In this noncompliant code example, the function `f()` accepts three parameters. The function copies `n` integers from the `int` array referenced by the `restrict`-qualified pointer `p` to the `int` array referenced by the `restrict`-qualified pointer `q`. Because the destination array is modified during each execution of the function (for which `n` is nonzero), if the array is accessed through one of the pointer parameters, it cannot also be accessed through the other. Declaring these function parameters as `restrict`-qualified pointers allows aggressive optimization by the compiler but can also result in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) if these pointers refer to overlapping objects.
+
+```cpp
+#include
+void f(size_t n, int *restrict p, const int *restrict q) {
+ while (n-- > 0) {
+ *p++ = *q++;
+ }
+}
+
+void g(void) {
+ extern int d[100];
+ /* ... */
+ f(50, d + 1, d); /* Undefined behavior */
+}
+```
+The function `g()` declares an array `d` consisting of 100 `int` values and then invokes `f()` to copy memory from one area of the array to another. This call has undefined behavior because each of `d[1]` through `d[49]` is accessed through both `p` and `q`.
+
+**Compliant Solution**
+
+In this compliant solution, the function `f()` is unchanged but the programmer has ensured that none of the calls to `f()` result in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). The call to `f()` in `g()` is valid because the storage allocated to `d` is effectively divided into two disjoint objects.
+
+```cpp
+#include
+void f(size_t n, int *restrict p, const int *restrict q) {
+ while (n-- > 0) {
+ *p++ = *q++;
+ }
+}
+
+void g(void) {
+ extern int d[100];
+ /* ... */
+ f(50, d + 50, d); /* Defined behavior */
+}
+```
+**Noncompliant Code Example**
+
+In this noncompliant code example, the function `add()` adds the integer array referenced by the `restrict`-qualified pointers lhs to the integer array referenced by the `restrict`-qualified pointer `rhs` and stores the result in the `restrict`-qualified pointer referenced by `res`. The function `f()` declares an array `a` consisting of 100 `int` values and then invokes `add()` to copy memory from one area of the array to another. The call `add(100, a, a, a)` has [undefined behavior ](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior)because the object modified by `res` is accessed by lhs and `rhs`.
+
+```cpp
+#include
+
+void add(size_t n, int *restrict res, const int *restrict lhs,
+ const int *restrict rhs) {
+ for (size_t i = 0; i < n; ++i) {
+ res[i] = lhs[i] + rhs[i];
+ }
+}
+
+void f(void) {
+ int a[100];
+ add(100, a, a, a); /* Undefined behavior */
+}
+```
+**Compliant Solution**
+
+In this compliant solution, an unmodified object is aliased through two restricted pointers. Because `a` and `b` are disjoint arrays, a call of the form `add(100, a, b, b)` has defined behavior, because array `b` is not modified within function `add`.
+
+```cpp
+#include
+void add(size_t n, int *restrict res, const int *restrict lhs,
+ const int *restrict rhs) {
+ for (size_t i = 0; i < n; ++i) {
+ res[i] = lhs[i] + rhs[i];
+ }
+}
+
+void f(void) {
+ int a[100];
+ int b[100];
+ add(100, a, b, b); /* Defined behavior */
+}
+```
+
+## Invoking Library Functions with restrict-Qualified Pointers
+
+Ensure that `restrict`-qualified source and destination pointers do not reference overlapping objects when invoking library functions. For example, the following table lists C standard library functions that copy memory from a source object referenced by a `restrict`-qualified pointer to a destination object that is also referenced by a `restrict`-qualified pointer:
+
+ Standard C | Annex K |
strcpy() | strcpy_s() |
strncpy() | strncpy_s() |
strcat() | strcat_s() |
strncat() | strncat_s() |
memcpy() | memcpy_s() |
| strtok_s() |
+If the objects referenced by arguments to functions overlap (meaning the objects share some common memory addresses), the behavior is [undefined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). (See also [undefined behavior 68](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_68).) The result of the functions is unknown, and data may be corrupted. As a result, these functions must never be passed pointers to overlapping objects. If data must be copied between objects that share common memory addresses, a copy function guaranteed to work on overlapping memory, such as `memmove()`, should be used.
+
+
+**Noncompliant Code Example**
+
+In this noncompliant code example, the values of objects referenced by `ptr1` and `ptr2` become unpredictable after the call to `memcpy()` because their memory areas overlap:
+
+```cpp
+#include
+
+void func(void) {
+ char c_str[]= "test string";
+ char *ptr1 = c_str;
+ char *ptr2;
+
+ ptr2 = ptr1 + 3;
+ /* Undefined behavior because of overlapping objects */
+ memcpy(ptr2, ptr1, 6);
+ /* ... */
+}
+```
+**Compliant Solution**
+
+In this compliant solution, the call to `memcpy()` is replaced with a call to `memmove()`. The `memmove()` function performs the same operation as `memcpy()` when the memory regions do not overlap. When the memory regions do overlap, the *n* characters from the object pointed to by the source (`ptr1`) are first copied into a temporary array of *n* characters that does not overlap the objects pointed to by the destination (`ptr2`) or the source. The *n* characters from the temporary array are then copied into the object pointed to by the destination.
+
+```cpp
+#include
+
+void func(void) {
+ char c_str[]= "test string";
+ char *ptr1 = c_str;
+ char *ptr2;
+
+ ptr2 = ptr1 + 3;
+ memmove(ptr2, ptr1, 6); /* Replace call to memcpy() */
+ /* ... */
+}
+```
+Similar solutions using `memmove()` can replace the string functions as long as care is taken regarding the byte size of the characters and proper null-termination of the copied string.
+
+## Calling Functions with restrict-Qualified Pointer to a const-Qualified Type
+
+Ensure that functions that accept a `restrict`-qualified pointer to a `const`-qualified type do not modify the object referenced by that pointer. Formatted input and output standard library functions frequently fit this description. The following table lists of some of the common functions for which the format argument is a `restrict`-qualified pointer to a `const`-qualified type.
+
+ Standard C | Annex K |
printf() | printf_s() |
scanf() | scanf_s() |
sprintf() | sprintf_s() |
snprintf() | snprintf_s() |
+For formatted output functions such as `printf()`, it is unlikely that a programmer would modify the format string. However, an attacker may attempt to do so if a program violates [FIO30-C. Exclude user input from format strings](https://wiki.sei.cmu.edu/confluence/display/c/FIO30-C.+Exclude+user+input+from+format+strings) and passes tainted values as part of the format string.
+
+
+**Noncompliant Code Example**
+
+In this noncompliant code example, the programmer is attempting to overwrite the format string with a string value read in from `stdin` such as `"%d%f 1 3.3"` and use the resulting modified string of `"%s%d%f"` to input the subsequent values of `1` and `3.3`:
+
+```cpp
+#include
+
+void func(void) {
+ int i;
+ float x;
+ char format[100] = "%s";
+ /* Undefined behavior */
+ int n = scanf(format, format + 2, &i, &x);
+ /* ... */
+}
+```
+**Compliant Solution**
+
+The intended results are achieved by this compliant solution:
+
+```cpp
+#include
+
+void func(void) {
+ int i;
+ float x;
+ int n = scanf("%d%f", &i, &x); /* Defined behavior */
+ /* ... */
+}
+```
+
+## Outer-to-Inner Assignments between Restricted Pointers
+
+The assignment between `restrict`-qualified pointers declared in an inner nested block from an outer block has defined behavior.
+
+**Noncompliant Code Example**
+
+The assignment of `restrict`-qualified pointers to other `restrict`-qualified pointers within the same block has [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior):
+
+```cpp
+void func(void) {
+ int *restrict p1;
+ int *restrict q1;
+
+ int *restrict p2 = p1; /* Undefined behavior */
+ int *restrict q2 = q1; /* Undefined behavior */
+ }
+```
+**Compliant Solution**
+
+The intended results can be achieved using an inner nested block, as shown in this compliant solution:
+
+```cpp
+void func(void) {
+ int *restrict p1;
+ int *restrict q1;
+ { /* Added inner block */
+ int *restrict p2 = p1; /* Valid, well-defined behavior */
+ int *restrict q2 = q1; /* Valid, well-defined behavior */
+ }
+}
+```
+
+## Risk Assessment
+
+The incorrect use of `restrict`-qualified pointers can result in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) that might be [exploited](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) to cause data integrity violations.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP43-C | Medium | Probable | High | P4 | L3 |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP43-C).
+
+## Automated Detection
+
+
+
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+1. MISRA Rule 8.14 prohibits the use of the restrict keyword except in C standard library functions.
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [EXP43-C: Avoid undefined behavior when using restrict-qualified pointers](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP43-C/DoNotPassAliasedPointerToRestrictQualifiedParam.ql b/c/cert/src/rules/EXP43-C/DoNotPassAliasedPointerToRestrictQualifiedParam.ql
new file mode 100644
index 0000000000..4aced57136
--- /dev/null
+++ b/c/cert/src/rules/EXP43-C/DoNotPassAliasedPointerToRestrictQualifiedParam.ql
@@ -0,0 +1,27 @@
+/**
+ * @id c/cert/do-not-pass-aliased-pointer-to-restrict-qualified-param
+ * @name EXP43-C: Do not pass aliased pointers to restrict-qualified parameters
+ * @description Passing an aliased pointer to a restrict-qualified parameter is undefined behavior.
+ * @kind problem
+ * @precision medium
+ * @problem.severity error
+ * @tags external/cert/id/exp43-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import codingstandards.c.cert
+import codingstandards.cpp.rules.donotpassaliasedpointertorestrictqualifiedparamshared.DoNotPassAliasedPointerToRestrictQualifiedParamShared
+
+class DoNotPassAliasedPointerToRestrictQualifiedParamQuery extends DoNotPassAliasedPointerToRestrictQualifiedParamSharedSharedQuery
+{
+ DoNotPassAliasedPointerToRestrictQualifiedParamQuery() {
+ this = Pointers3Package::doNotPassAliasedPointerToRestrictQualifiedParamQuery()
+ }
+}
diff --git a/c/cert/src/rules/EXP43-C/RestrictPointerReferencesOverlappingObject.md b/c/cert/src/rules/EXP43-C/RestrictPointerReferencesOverlappingObject.md
new file mode 100644
index 0000000000..ce02712b72
--- /dev/null
+++ b/c/cert/src/rules/EXP43-C/RestrictPointerReferencesOverlappingObject.md
@@ -0,0 +1,296 @@
+# EXP43-C: Do not assign the value of a restrict-qualified pointer to another restrict-qualified pointer
+
+This query implements the CERT-C rule EXP43-C:
+
+> Avoid undefined behavior when using restrict-qualified pointers
+
+
+## Description
+
+An object that is accessed through a `restrict`-qualified pointer has a special association with that pointer. This association requires that all accesses to that object use, directly or indirectly, the value of that particular pointer. The intended use of the restrict qualifier is to promote optimization, and deleting all instances of the qualifier from a program does not change its meaning (that is, observable behavior). In the absence of this qualifier, other pointers can alias this object. Caching the value in an object designated through a `restrict`-qualified pointer is safe at the beginning of the block in which the pointer is declared because no preexisting aliases may also be used to reference that object. The cached value must be restored to the object by the end of the block, where preexisting aliases again become available. New aliases may be formed within the block, but these must all depend on the value of the `restrict`-qualified pointer so that they can be identified and adjusted to refer to the cached value. For a `restrict`-qualified pointer at file scope, the block is the body of each function in the file \[[Walls 2006](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography)\]. Developers should be aware that C++ does not support the `restrict` qualifier, but some C++ compiler implementations support an equivalent qualifier as an extension.
+
+The C Standard \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\] identifies the following [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior):
+
+> A restrict-qualified pointer is assigned a value based on another restricted pointer whose associated block neither began execution before the block associated with this pointer, nor ended before the assignment (6.7.3.1).
+
+
+This is an oversimplification, however, and it is important to review the formal definition of *restrict* in subclause 6.7.3.1 of the C Standard to properly understand undefined behaviors associated with the use of `restrict`-qualified pointers.
+
+## Overlapping Objects
+
+The `restrict` qualifier requires that the pointers do not reference overlapping objects. If the objects referenced by arguments to functions overlap (meaning the objects share some common memory addresses), the behavior is undefined.
+
+**Noncompliant Code Example**
+
+This code example is noncompliant because an assignment is made between two `restrict`-qualified pointers in the same scope:
+
+```cpp
+int *restrict a;
+int *restrict b;
+
+extern int c[];
+
+int main(void) {
+ c[0] = 17;
+ c[1] = 18;
+ a = &c[0];
+ b = &c[1];
+ a = b; /* Undefined behavior */
+ /* ... */
+}
+```
+Note that [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) occurs only when `a` is assigned to `b`. It is valid for `a` and `b` to point into the same array object, provided the range of elements accessed through one of the pointers does not overlap with the range of elements accessed through the other pointer.
+
+**Compliant Solution**
+
+One way to eliminate the [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) is simply to remove the `restrict-`qualification from the affected pointers:
+
+```cpp
+int *a;
+int *b;
+
+extern int c[];
+
+int main(void) {
+ c[0] = 17;
+ c[1] = 18;
+ a = &c[0];
+ b = &c[1];
+ a = b; /* Defined behavior */
+ /* ... */
+}
+```
+
+## restrict-Qualified Function Parameters
+
+When calling functions that have `restrict`-qualified function parameters, it is important that the pointer arguments do not reference overlapping objects if one or more of the pointers are used to modify memory. Consequently, it is important to understand the semantics of the function being called.
+
+**Noncompliant Code Example**
+
+In this noncompliant code example, the function `f()` accepts three parameters. The function copies `n` integers from the `int` array referenced by the `restrict`-qualified pointer `p` to the `int` array referenced by the `restrict`-qualified pointer `q`. Because the destination array is modified during each execution of the function (for which `n` is nonzero), if the array is accessed through one of the pointer parameters, it cannot also be accessed through the other. Declaring these function parameters as `restrict`-qualified pointers allows aggressive optimization by the compiler but can also result in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) if these pointers refer to overlapping objects.
+
+```cpp
+#include
+void f(size_t n, int *restrict p, const int *restrict q) {
+ while (n-- > 0) {
+ *p++ = *q++;
+ }
+}
+
+void g(void) {
+ extern int d[100];
+ /* ... */
+ f(50, d + 1, d); /* Undefined behavior */
+}
+```
+The function `g()` declares an array `d` consisting of 100 `int` values and then invokes `f()` to copy memory from one area of the array to another. This call has undefined behavior because each of `d[1]` through `d[49]` is accessed through both `p` and `q`.
+
+**Compliant Solution**
+
+In this compliant solution, the function `f()` is unchanged but the programmer has ensured that none of the calls to `f()` result in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). The call to `f()` in `g()` is valid because the storage allocated to `d` is effectively divided into two disjoint objects.
+
+```cpp
+#include
+void f(size_t n, int *restrict p, const int *restrict q) {
+ while (n-- > 0) {
+ *p++ = *q++;
+ }
+}
+
+void g(void) {
+ extern int d[100];
+ /* ... */
+ f(50, d + 50, d); /* Defined behavior */
+}
+```
+**Noncompliant Code Example**
+
+In this noncompliant code example, the function `add()` adds the integer array referenced by the `restrict`-qualified pointers lhs to the integer array referenced by the `restrict`-qualified pointer `rhs` and stores the result in the `restrict`-qualified pointer referenced by `res`. The function `f()` declares an array `a` consisting of 100 `int` values and then invokes `add()` to copy memory from one area of the array to another. The call `add(100, a, a, a)` has [undefined behavior ](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior)because the object modified by `res` is accessed by lhs and `rhs`.
+
+```cpp
+#include
+
+void add(size_t n, int *restrict res, const int *restrict lhs,
+ const int *restrict rhs) {
+ for (size_t i = 0; i < n; ++i) {
+ res[i] = lhs[i] + rhs[i];
+ }
+}
+
+void f(void) {
+ int a[100];
+ add(100, a, a, a); /* Undefined behavior */
+}
+```
+**Compliant Solution**
+
+In this compliant solution, an unmodified object is aliased through two restricted pointers. Because `a` and `b` are disjoint arrays, a call of the form `add(100, a, b, b)` has defined behavior, because array `b` is not modified within function `add`.
+
+```cpp
+#include
+void add(size_t n, int *restrict res, const int *restrict lhs,
+ const int *restrict rhs) {
+ for (size_t i = 0; i < n; ++i) {
+ res[i] = lhs[i] + rhs[i];
+ }
+}
+
+void f(void) {
+ int a[100];
+ int b[100];
+ add(100, a, b, b); /* Defined behavior */
+}
+```
+
+## Invoking Library Functions with restrict-Qualified Pointers
+
+Ensure that `restrict`-qualified source and destination pointers do not reference overlapping objects when invoking library functions. For example, the following table lists C standard library functions that copy memory from a source object referenced by a `restrict`-qualified pointer to a destination object that is also referenced by a `restrict`-qualified pointer:
+
+ Standard C | Annex K |
strcpy() | strcpy_s() |
strncpy() | strncpy_s() |
strcat() | strcat_s() |
strncat() | strncat_s() |
memcpy() | memcpy_s() |
| strtok_s() |
+If the objects referenced by arguments to functions overlap (meaning the objects share some common memory addresses), the behavior is [undefined](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior). (See also [undefined behavior 68](https://wiki.sei.cmu.edu/confluence/display/c/CC.+Undefined+Behavior#CC.UndefinedBehavior-ub_68).) The result of the functions is unknown, and data may be corrupted. As a result, these functions must never be passed pointers to overlapping objects. If data must be copied between objects that share common memory addresses, a copy function guaranteed to work on overlapping memory, such as `memmove()`, should be used.
+
+
+**Noncompliant Code Example**
+
+In this noncompliant code example, the values of objects referenced by `ptr1` and `ptr2` become unpredictable after the call to `memcpy()` because their memory areas overlap:
+
+```cpp
+#include
+
+void func(void) {
+ char c_str[]= "test string";
+ char *ptr1 = c_str;
+ char *ptr2;
+
+ ptr2 = ptr1 + 3;
+ /* Undefined behavior because of overlapping objects */
+ memcpy(ptr2, ptr1, 6);
+ /* ... */
+}
+```
+**Compliant Solution**
+
+In this compliant solution, the call to `memcpy()` is replaced with a call to `memmove()`. The `memmove()` function performs the same operation as `memcpy()` when the memory regions do not overlap. When the memory regions do overlap, the *n* characters from the object pointed to by the source (`ptr1`) are first copied into a temporary array of *n* characters that does not overlap the objects pointed to by the destination (`ptr2`) or the source. The *n* characters from the temporary array are then copied into the object pointed to by the destination.
+
+```cpp
+#include
+
+void func(void) {
+ char c_str[]= "test string";
+ char *ptr1 = c_str;
+ char *ptr2;
+
+ ptr2 = ptr1 + 3;
+ memmove(ptr2, ptr1, 6); /* Replace call to memcpy() */
+ /* ... */
+}
+```
+Similar solutions using `memmove()` can replace the string functions as long as care is taken regarding the byte size of the characters and proper null-termination of the copied string.
+
+## Calling Functions with restrict-Qualified Pointer to a const-Qualified Type
+
+Ensure that functions that accept a `restrict`-qualified pointer to a `const`-qualified type do not modify the object referenced by that pointer. Formatted input and output standard library functions frequently fit this description. The following table lists of some of the common functions for which the format argument is a `restrict`-qualified pointer to a `const`-qualified type.
+
+ Standard C | Annex K |
printf() | printf_s() |
scanf() | scanf_s() |
sprintf() | sprintf_s() |
snprintf() | snprintf_s() |
+For formatted output functions such as `printf()`, it is unlikely that a programmer would modify the format string. However, an attacker may attempt to do so if a program violates [FIO30-C. Exclude user input from format strings](https://wiki.sei.cmu.edu/confluence/display/c/FIO30-C.+Exclude+user+input+from+format+strings) and passes tainted values as part of the format string.
+
+
+**Noncompliant Code Example**
+
+In this noncompliant code example, the programmer is attempting to overwrite the format string with a string value read in from `stdin` such as `"%d%f 1 3.3"` and use the resulting modified string of `"%s%d%f"` to input the subsequent values of `1` and `3.3`:
+
+```cpp
+#include
+
+void func(void) {
+ int i;
+ float x;
+ char format[100] = "%s";
+ /* Undefined behavior */
+ int n = scanf(format, format + 2, &i, &x);
+ /* ... */
+}
+```
+**Compliant Solution**
+
+The intended results are achieved by this compliant solution:
+
+```cpp
+#include
+
+void func(void) {
+ int i;
+ float x;
+ int n = scanf("%d%f", &i, &x); /* Defined behavior */
+ /* ... */
+}
+```
+
+## Outer-to-Inner Assignments between Restricted Pointers
+
+The assignment between `restrict`-qualified pointers declared in an inner nested block from an outer block has defined behavior.
+
+**Noncompliant Code Example**
+
+The assignment of `restrict`-qualified pointers to other `restrict`-qualified pointers within the same block has [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior):
+
+```cpp
+void func(void) {
+ int *restrict p1;
+ int *restrict q1;
+
+ int *restrict p2 = p1; /* Undefined behavior */
+ int *restrict q2 = q1; /* Undefined behavior */
+ }
+```
+**Compliant Solution**
+
+The intended results can be achieved using an inner nested block, as shown in this compliant solution:
+
+```cpp
+void func(void) {
+ int *restrict p1;
+ int *restrict q1;
+ { /* Added inner block */
+ int *restrict p2 = p1; /* Valid, well-defined behavior */
+ int *restrict q2 = q1; /* Valid, well-defined behavior */
+ }
+}
+```
+
+## Risk Assessment
+
+The incorrect use of `restrict`-qualified pointers can result in [undefined behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-undefinedbehavior) that might be [exploited](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) to cause data integrity violations.
+
+ Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
EXP43-C | Medium | Probable | High | P4 | L3 |
+
+
+## Related Vulnerabilities
+
+Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+EXP43-C).
+
+## Automated Detection
+
+
+
+
+## Related Guidelines
+
+[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
+
+
+1. MISRA Rule 8.14 prohibits the use of the restrict keyword except in C standard library functions.
+
+
+## Bibliography
+
+
+
+
+## Implementation notes
+
+None
+
+## References
+
+* CERT-C: [EXP43-C: Avoid undefined behavior when using restrict-qualified pointers](https://wiki.sei.cmu.edu/confluence/display/c)
diff --git a/c/cert/src/rules/EXP43-C/RestrictPointerReferencesOverlappingObject.ql b/c/cert/src/rules/EXP43-C/RestrictPointerReferencesOverlappingObject.ql
new file mode 100644
index 0000000000..31618785d2
--- /dev/null
+++ b/c/cert/src/rules/EXP43-C/RestrictPointerReferencesOverlappingObject.ql
@@ -0,0 +1,90 @@
+/**
+ * @id c/cert/restrict-pointer-references-overlapping-object
+ * @name EXP43-C: Do not assign the value of a restrict-qualified pointer to another restrict-qualified pointer
+ * @description Restrict qualified pointers referencing overlapping objects is undefined behavior.
+ * @kind problem
+ * @precision high
+ * @problem.severity error
+ * @tags external/cert/id/exp43-c
+ * correctness
+ * external/cert/severity/medium
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/high
+ * external/cert/priority/p4
+ * external/cert/level/l3
+ * external/cert/obligation/rule
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.DataFlow
+import semmle.code.cpp.controlflow.Dominance
+import codingstandards.c.cert
+import codingstandards.cpp.Variable
+
+/**
+ * An `Expr` that is an assignment or initialization to a restrict-qualified pointer-type variable.
+ */
+class AssignmentOrInitializationToRestrictPtrValueExpr extends Expr {
+ Variable v;
+
+ AssignmentOrInitializationToRestrictPtrValueExpr() {
+ this = v.getAnAssignedValue() and
+ v.getType().hasSpecifier("restrict")
+ }
+
+ Variable getVariable() { result = v }
+
+ predicate isTargetRestrictQualifiedAndInSameScope() {
+ this.(VariableAccess).getTarget().getType().hasSpecifier("restrict") and
+ this.(VariableAccess).getTarget().getParentScope() = this.getVariable().getParentScope()
+ }
+}
+
+/**
+ * A data-flow configuration for tracking flow from an assignment or initialization to
+ * an assignment to an `AssignmentOrInitializationToRestrictPtrValueExpr`.
+ */
+module AssignedValueToRestrictPtrValueConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ exists(Variable v | source.asExpr() = v.getAnAssignedValue())
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ sink.asExpr() instanceof AssignmentOrInitializationToRestrictPtrValueExpr
+ }
+}
+
+module AssignedValueToRestrictPtrValueFlow =
+ DataFlow::Global;
+
+from
+ AssignmentOrInitializationToRestrictPtrValueExpr expr, DataFlow::Node sourceValue,
+ string sourceMessage
+where
+ not isExcluded(expr, Pointers3Package::restrictPointerReferencesOverlappingObjectQuery()) and
+ (
+ // Two restrict-qualified pointers in the same scope assigned to each other
+ expr.isTargetRestrictQualifiedAndInSameScope() and
+ sourceValue.asExpr() = expr and
+ sourceMessage = "the object pointed to by " + expr.(VariableAccess).getTarget().getName()
+ or
+ // If the same expressions flows to two unique `AssignmentOrInitializationToRestrictPtrValueExpr`
+ // in the same block, then the two variables point to the same (overlapping) object
+ not expr.isTargetRestrictQualifiedAndInSameScope() and
+ exists(AssignmentOrInitializationToRestrictPtrValueExpr pre_expr |
+ expr.getEnclosingBlock() = pre_expr.getEnclosingBlock() and
+ (
+ AssignedValueToRestrictPtrValueFlow::flow(sourceValue, DataFlow::exprNode(pre_expr)) and
+ AssignedValueToRestrictPtrValueFlow::flow(sourceValue, DataFlow::exprNode(expr)) and
+ sourceMessage = "the same source value"
+ or
+ // Expressions referring to the address of the same variable can also result in aliasing
+ getAddressOfExprTargetBase(expr) = getAddressOfExprTargetBase(pre_expr) and
+ sourceValue.asExpr() = pre_expr and
+ sourceMessage = getAddressOfExprTargetBase(expr).getName() + " via address-of"
+ ) and
+ strictlyDominates(pragma[only_bind_out](pre_expr), pragma[only_bind_out](expr))
+ )
+ )
+select expr, "Assignment to restrict-qualified pointer $@ results in pointers aliasing $@.",
+ expr.getVariable(), expr.getVariable().getName(), sourceValue, sourceMessage
diff --git a/c/cert/src/rules/EXP44-C/UnevaluatedOperandWithSideEffect.ql b/c/cert/src/rules/EXP44-C/UnevaluatedOperandWithSideEffect.ql
index 32d30a09ad..02d71b3497 100644
--- a/c/cert/src/rules/EXP44-C/UnevaluatedOperandWithSideEffect.ql
+++ b/c/cert/src/rules/EXP44-C/UnevaluatedOperandWithSideEffect.ql
@@ -9,6 +9,11 @@
* @problem.severity error
* @tags external/cert/id/exp44-c
* correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/low
+ * external/cert/priority/p3
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/EXP45-C/AssignmentsInSelectionStatements.ql b/c/cert/src/rules/EXP45-C/AssignmentsInSelectionStatements.ql
index f6e29eb28c..c831713486 100644
--- a/c/cert/src/rules/EXP45-C/AssignmentsInSelectionStatements.ql
+++ b/c/cert/src/rules/EXP45-C/AssignmentsInSelectionStatements.ql
@@ -8,6 +8,11 @@
* @problem.severity error
* @tags external/cert/id/exp45-c
* correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p6
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/EXP46-C/DoNotUseABitwiseOperatorWithABooleanLikeOperand.ql b/c/cert/src/rules/EXP46-C/DoNotUseABitwiseOperatorWithABooleanLikeOperand.ql
index 040a8bb6ee..549e57236a 100644
--- a/c/cert/src/rules/EXP46-C/DoNotUseABitwiseOperatorWithABooleanLikeOperand.ql
+++ b/c/cert/src/rules/EXP46-C/DoNotUseABitwiseOperatorWithABooleanLikeOperand.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/exp46-c
* maintainability
* readability
+ * external/cert/severity/low
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/low
+ * external/cert/priority/p9
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/FIO30-C/ExcludeUserInputFromFormatStrings.ql b/c/cert/src/rules/FIO30-C/ExcludeUserInputFromFormatStrings.ql
index b9df838b06..81ecf56ccf 100644
--- a/c/cert/src/rules/FIO30-C/ExcludeUserInputFromFormatStrings.ql
+++ b/c/cert/src/rules/FIO30-C/ExcludeUserInputFromFormatStrings.ql
@@ -8,6 +8,11 @@
* @tags external/cert/id/fio30-c
* correctness
* security
+ * external/cert/severity/high
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p18
+ * external/cert/level/l1
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/FIO32-C/DoNotPerformFileOperationsOnDevices.ql b/c/cert/src/rules/FIO32-C/DoNotPerformFileOperationsOnDevices.ql
index 2d16b2ffea..78817d31e9 100644
--- a/c/cert/src/rules/FIO32-C/DoNotPerformFileOperationsOnDevices.ql
+++ b/c/cert/src/rules/FIO32-C/DoNotPerformFileOperationsOnDevices.ql
@@ -8,15 +8,21 @@
* @tags external/cert/id/fio32-c
* correctness
* security
+ * external/cert/severity/medium
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
import cpp
import codingstandards.c.cert
import semmle.code.cpp.security.FunctionWithWrappers
-import semmle.code.cpp.security.Security
-import semmle.code.cpp.security.TaintTracking
-import TaintedWithPath
+import semmle.code.cpp.security.FlowSources
+import semmle.code.cpp.ir.IR
+import semmle.code.cpp.ir.dataflow.TaintTracking
+import TaintedPath::PathGraph
// Query TaintedPath.ql from the CodeQL standard library
/**
@@ -45,20 +51,60 @@ class FileFunction extends FunctionWithWrappers {
override predicate interestingArg(int arg) { arg = 0 }
}
-class TaintedPathConfiguration extends TaintTrackingConfiguration {
- override predicate isSink(Element tainted) {
- exists(FileFunction fileFunction | fileFunction.outermostWrapperFunctionCall(tainted, _))
+/**
+ * Holds for a variable that has any kind of upper-bound check anywhere in the program.
+ * This is biased towards being inclusive and being a coarse overapproximation because
+ * there are a lot of valid ways of doing an upper bounds checks if we don't consider
+ * where it occurs, for example:
+ * ```cpp
+ * if (x < 10) { sink(x); }
+ *
+ * if (10 > y) { sink(y); }
+ *
+ * if (z > 10) { z = 10; }
+ * sink(z);
+ * ```
+ */
+predicate hasUpperBoundsCheck(Variable var) {
+ exists(RelationalOperation oper, VariableAccess access |
+ oper.getAnOperand() = access and
+ access.getTarget() = var and
+ // Comparing to 0 is not an upper bound check
+ not oper.getAnOperand().getValue() = "0"
+ )
+}
+
+module TaintedPathConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node node) { node instanceof FlowSource }
+
+ predicate isSink(DataFlow::Node node) {
+ exists(FileFunction fileFunction |
+ fileFunction.outermostWrapperFunctionCall(node.asIndirectArgument(), _)
+ )
+ }
+
+ predicate isBarrier(DataFlow::Node node) {
+ node.asExpr().(Call).getTarget().getUnspecifiedType() instanceof ArithmeticType
+ or
+ exists(LoadInstruction load, Variable checkedVar |
+ load = node.asInstruction() and
+ checkedVar = load.getSourceAddress().(VariableAddressInstruction).getAstVariable() and
+ hasUpperBoundsCheck(checkedVar)
+ )
}
}
+module TaintedPath = TaintTracking::Global;
+
from
- FileFunction fileFunction, Expr taintedArg, Expr taintSource, PathNode sourceNode,
- PathNode sinkNode, string taintCause, string callChain
+ FileFunction fileFunction, Expr taintedArg, FlowSource taintSource,
+ TaintedPath::PathNode sourceNode, TaintedPath::PathNode sinkNode, string callChain
where
not isExcluded(taintedArg, IO3Package::doNotPerformFileOperationsOnDevicesQuery()) and
+ taintedArg = sinkNode.getNode().asIndirectArgument() and
fileFunction.outermostWrapperFunctionCall(taintedArg, callChain) and
- taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
- isUserInput(taintSource, taintCause)
+ TaintedPath::flowPath(sourceNode, sinkNode) and
+ taintSource = sourceNode.getNode()
select taintedArg, sourceNode, sinkNode,
- "This argument to a file access function is derived from $@ and then passed to " + callChain,
- taintSource, "user input (" + taintCause + ")"
+ "This argument to a file access function is derived from $@ and then passed to " + callChain + ".",
+ taintSource, "user input (" + taintSource.getSourceType() + ")"
diff --git a/c/cert/src/rules/FIO34-C/DistinguishBetweenCharReadFromAFileAndEofOrWeof.ql b/c/cert/src/rules/FIO34-C/DistinguishBetweenCharReadFromAFileAndEofOrWeof.ql
index a55c2dbf29..01c13e642b 100644
--- a/c/cert/src/rules/FIO34-C/DistinguishBetweenCharReadFromAFileAndEofOrWeof.ql
+++ b/c/cert/src/rules/FIO34-C/DistinguishBetweenCharReadFromAFileAndEofOrWeof.ql
@@ -8,6 +8,11 @@
* @tags external/cert/id/fio34-c
* correctness
* security
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p12
+ * external/cert/level/l1
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/FIO34-C/EndOfFileCheckPortability.ql b/c/cert/src/rules/FIO34-C/EndOfFileCheckPortability.ql
index 274514e598..3336a059cd 100644
--- a/c/cert/src/rules/FIO34-C/EndOfFileCheckPortability.ql
+++ b/c/cert/src/rules/FIO34-C/EndOfFileCheckPortability.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/fio34-c
* correctness
* security
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p12
+ * external/cert/level/l1
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/FIO37-C/SuccessfulFgetsOrFgetwsMayReturnAnEmptyString.ql b/c/cert/src/rules/FIO37-C/SuccessfulFgetsOrFgetwsMayReturnAnEmptyString.ql
index 54f555d7cb..ad3a2c8192 100644
--- a/c/cert/src/rules/FIO37-C/SuccessfulFgetsOrFgetwsMayReturnAnEmptyString.ql
+++ b/c/cert/src/rules/FIO37-C/SuccessfulFgetsOrFgetwsMayReturnAnEmptyString.ql
@@ -7,6 +7,11 @@
* @problem.severity error
* @tags external/cert/id/fio37-c
* correctness
+ * external/cert/severity/high
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p12
+ * external/cert/level/l1
* external/cert/obligation/rule
*/
@@ -14,7 +19,7 @@ import cpp
import codingstandards.c.cert
import codingstandards.cpp.FgetsErrorManagement
import codingstandards.cpp.Dereferenced
-import semmle.code.cpp.dataflow.TaintTracking
+import semmle.code.cpp.dataflow.DataFlow
/*
* CFG nodes that follows a successful call to `fgets`
diff --git a/c/cert/src/rules/FIO38-C/DoNotCopyAFileObject.ql b/c/cert/src/rules/FIO38-C/DoNotCopyAFileObject.ql
index e8e897009e..5b5a043395 100644
--- a/c/cert/src/rules/FIO38-C/DoNotCopyAFileObject.ql
+++ b/c/cert/src/rules/FIO38-C/DoNotCopyAFileObject.ql
@@ -8,6 +8,11 @@
* @tags external/cert/id/fio38-c
* correctness
* security
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/FIO39-C/DoNotAlternatelyIOFromAStreamWithoutPositioning.ql b/c/cert/src/rules/FIO39-C/DoNotAlternatelyIOFromAStreamWithoutPositioning.ql
index 4983e3a69a..09289d1f79 100644
--- a/c/cert/src/rules/FIO39-C/DoNotAlternatelyIOFromAStreamWithoutPositioning.ql
+++ b/c/cert/src/rules/FIO39-C/DoNotAlternatelyIOFromAStreamWithoutPositioning.ql
@@ -8,6 +8,11 @@
* @problem.severity error
* @tags external/cert/id/fio39-c
* correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/likely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p6
+ * external/cert/level/l2
* external/cert/obligation/rule
*/
@@ -15,7 +20,8 @@ import cpp
import codingstandards.c.cert
import codingstandards.cpp.rules.iofstreammissingpositioning.IOFstreamMissingPositioning
-class DoNotAlternatelyIOFromAStreamWithoutPositioningQuery extends IOFstreamMissingPositioningSharedQuery {
+class DoNotAlternatelyIOFromAStreamWithoutPositioningQuery extends IOFstreamMissingPositioningSharedQuery
+{
DoNotAlternatelyIOFromAStreamWithoutPositioningQuery() {
this = IO1Package::doNotAlternatelyIOFromAStreamWithoutPositioningQuery()
}
diff --git a/c/cert/src/rules/FIO40-C/ResetStringsOnFgetsOrFgetwsFailure.ql b/c/cert/src/rules/FIO40-C/ResetStringsOnFgetsOrFgetwsFailure.ql
index 69fb92a15c..9b0882ac66 100644
--- a/c/cert/src/rules/FIO40-C/ResetStringsOnFgetsOrFgetwsFailure.ql
+++ b/c/cert/src/rules/FIO40-C/ResetStringsOnFgetsOrFgetwsFailure.ql
@@ -9,6 +9,11 @@
* @tags external/cert/id/fio40-c
* correctness
* security
+ * external/cert/severity/low
+ * external/cert/likelihood/probable
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p4
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
@@ -16,6 +21,7 @@ import cpp
import codingstandards.cpp.FgetsErrorManagement
import codingstandards.cpp.Dereferenced
import codingstandards.c.cert
+import semmle.code.cpp.dataflow.DataFlow
/*
* Models calls to `memcpy` `strcpy` `strncpy` and their wrappers
diff --git a/c/cert/src/rules/FIO41-C/DoNotCallGetcAndPutcWithSideEffects.ql b/c/cert/src/rules/FIO41-C/DoNotCallGetcAndPutcWithSideEffects.ql
index 7fc1c11d26..5c7d759606 100644
--- a/c/cert/src/rules/FIO41-C/DoNotCallGetcAndPutcWithSideEffects.ql
+++ b/c/cert/src/rules/FIO41-C/DoNotCallGetcAndPutcWithSideEffects.ql
@@ -8,6 +8,11 @@
* @problem.severity error
* @tags external/cert/id/fio41-c
* correctness
+ * external/cert/severity/low
+ * external/cert/likelihood/unlikely
+ * external/cert/remediation-cost/medium
+ * external/cert/priority/p2
+ * external/cert/level/l3
* external/cert/obligation/rule
*/
diff --git a/c/cert/src/rules/FIO42-C/CloseFilesWhenTheyAreNoLongerNeeded.md b/c/cert/src/rules/FIO42-C/CloseFilesWhenTheyAreNoLongerNeeded.md
index 91654e8ee2..f84163ae4a 100644
--- a/c/cert/src/rules/FIO42-C/CloseFilesWhenTheyAreNoLongerNeeded.md
+++ b/c/cert/src/rules/FIO42-C/CloseFilesWhenTheyAreNoLongerNeeded.md
@@ -180,7 +180,7 @@ Failing to properly close files may allow an attacker to exhaust system resource
This rule is stricter than rule \[fileclose\] in [ISO/IEC TS 17961:2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IECTS17961). Analyzers that conform to the technical standard may not detect all violations of this rule.
-