Compare commits

..

1 Commits

Author SHA1 Message Date
Abhinav Agarwal 5393ddf795 ci: add CodeQL static analysis
- New codeql.yml with manual build mode for C/C++ analysis
- Uses security-extended query suite, triggers on push/PR to master plus weekly schedule
- Upgraded to CodeQL Action v4 with full SHA pins
- Permissions scoped to contents: read and security-events: write
- Pin runner to ubuntu-24.04, add setup-python for PEP 668 compatibility, add job timeout
2026-05-21 18:35:19 -07:00
5 changed files with 57 additions and 137 deletions
+51
View File
@@ -0,0 +1,51 @@
name: CodeQL
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
security-events: write
jobs:
analyze:
name: Analyze C
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Initialize CodeQL
uses: github/codeql-action/init@bc0b696b4103f5fe60f15749af68a046868d511a # codeql-bundle-v2.25.4
with:
languages: c-cpp
build-mode: manual
queries: security-extended
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc ninja-build pkg-config libglib2.0-dev libfuse3-dev
pip3 install meson
- name: Build
run: |
meson setup build --buildtype=debug
ninja -C build sshfs
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@bc0b696b4103f5fe60f15749af68a046868d511a # codeql-bundle-v2.25.4
-79
View File
@@ -1,79 +0,0 @@
name: valgrind
on:
push:
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
valgrind-memcheck:
name: Valgrind memcheck
runs-on: ubuntu-24.04
timeout-minutes: 35
continue-on-error: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y valgrind gcc ninja-build libglib2.0-dev libfuse3-dev openssh-server openssh-client fuse3
pip3 install meson pytest pytest-timeout
- name: Build
run: |
meson setup build
ninja -C build
- name: Setup SSH
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -q -N ""
cat ~/.ssh/id_ed25519.pub > ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
sudo systemctl start ssh || sudo service ssh start
ssh -o StrictHostKeyChecking=no -o BatchMode=yes localhost true
- name: Check FUSE availability
run: |
test -e /dev/fuse
command -v fusermount3
- name: Create Valgrind log directory
run: mkdir -p valgrind-logs
- name: Run tests under Valgrind memcheck
timeout-minutes: 30
env:
TEST_WITH_VALGRIND: "true"
VALGRIND_OPTIONS: "--tool=memcheck --leak-check=full --show-leak-kinds=definite,indirect --errors-for-leak-kinds=definite,indirect --error-exitcode=99 --num-callers=25 --suppressions=${{ github.workspace }}/test/valgrind.supp --log-file=${{ github.workspace }}/valgrind-logs/memcheck.%p.log -q"
G_DEBUG: "fatal-warnings,gc-friendly"
G_SLICE: "always-malloc"
run: |
cd build
python3 -m pytest -q --tb=short --maxfail=99 --timeout=300 \
test/ \
--junitxml=test-results-valgrind.xml
- name: Upload test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-results-valgrind
path: |
build/test-results-valgrind.xml
build/meson-logs/
valgrind-logs/
-14
View File
@@ -1,5 +1,4 @@
import sys
import os
import pytest
import time
import re
@@ -86,13 +85,6 @@ def register_output(self, pattern, count=1, flags=re.MULTILINE):
current_capfd = None
_running_with_valgrind = os.environ.get("TEST_WITH_VALGRIND", "no").lower().strip() not in (
"no",
"false",
"0",
)
@pytest.fixture(autouse=True)
def save_cap_fixtures(request, capfd):
global current_capfd
@@ -101,12 +93,6 @@ def save_cap_fixtures(request, capfd):
# Monkeypatch in a function to register false positives
type(capfd).register_output = register_output
# When running under Valgrind, its ==pid== summary lines on stderr are
# expected. Register them as false positives so check_test_output does
# not mistake them for suspicious output.
if _running_with_valgrind:
capfd.false_positives.append((r"^==[0-9]+==[^\n]*\n", re.MULTILINE, 0))
if request.config.getoption("capture") == "no":
capfd = None
current_capfd = capfd
+6 -19
View File
@@ -9,14 +9,6 @@ from contextlib import contextmanager
basename = pjoin(os.path.dirname(__file__), "..")
_valgrind_timeout_multiplier = (
4
if os.environ.get("TEST_WITH_VALGRIND", "no").lower().strip()
not in ("no", "false", "0")
else 1
)
_mount_timeout = 30 * _valgrind_timeout_multiplier
def os_create(name):
os.close(os.open(name, os.O_CREAT | os.O_RDWR))
@@ -33,7 +25,7 @@ def os_open(name, flags):
def wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount):
elapsed = 0
while elapsed < _mount_timeout:
while elapsed < 30:
if test_fn(mnt_dir):
return True
if mount_process.poll() is not None:
@@ -45,7 +37,7 @@ def wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount):
def cleanup(mount_process, mnt_dir):
subprocess.call(
["fusermount3", "-z", "-u", mnt_dir],
["fusermount", "-z", "-u", mnt_dir],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
)
@@ -63,7 +55,7 @@ def umount(mount_process, mnt_dir):
# Give mount process a little while to terminate. Popen.wait(timeout)
# was only added in 3.3...
elapsed = 0
while elapsed < _mount_timeout:
while elapsed < 30:
code = mount_process.poll()
if code is not None:
if code == 0:
@@ -101,12 +93,12 @@ def fuse_test_marker():
return pytest.mark.skip(reason=reason)
with subprocess.Popen(
["which", "fusermount3"], stdout=subprocess.PIPE, universal_newlines=True
["which", "fusermount"], stdout=subprocess.PIPE, universal_newlines=True
) as which:
fusermount_path = which.communicate()[0].strip()
if not fusermount_path or which.returncode != 0:
return skip("Can't find fusermount3 executable")
return skip("Can't find fusermount executable")
if not os.path.exists("/dev/fuse"):
return skip("FUSE kernel module does not seem to be loaded")
@@ -134,11 +126,6 @@ if os.environ.get("TEST_WITH_VALGRIND", "no").lower().strip() not in (
"false",
"0",
):
import shlex
valgrind_options_env = os.environ.get("VALGRIND_OPTIONS", "")
if valgrind_options_env:
base_cmdline = ["valgrind"] + shlex.split(valgrind_options_env) + ["--"]
else:
base_cmdline = ["valgrind", "-q", "--"]
base_cmdline = ["valgrind", "-q", "--"]
else:
base_cmdline = []
-25
View File
@@ -1,25 +0,0 @@
# Valgrind suppression file for sshfs tests.
#
# Keep this file minimal. Only suppress stacks that originate entirely in
# third-party libraries (GLib, libfuse, glibc, pthreads) and have been
# confirmed as false positives or benign teardown noise.
#
# Do NOT suppress any stack frame that includes sshfs.c or cache.c unless
# there is a documented upstream false positive with a linked note below.
#
# To generate candidates locally:
# TEST_WITH_VALGRIND=true VALGRIND_OPTIONS="--tool=memcheck --leak-check=full \
# --gen-suppressions=all -q --" python3 -m pytest test/test_sshfs.py ...
# libfuse allocates thread-local or worker-thread state inside its shared
# library during pthread_create. These are not reachable after the threads
# exit but are never explicitly freed — they are benign teardown leaks in
# libfuse internals, not sshfs bugs.
{
libfuse-worker-thread-alloc
Memcheck:Leak
match-leak-kinds: definite,indirect
fun:calloc
...
obj:*/libfuse3.so*
}