Compare commits

..

1 Commits

Author SHA1 Message Date
Abhinav Agarwal d9ec8e7d28 ci: harden workflow with SHA pins, permissions, timeouts, and dependabot
- Pin all actions to Node 24-capable full SHAs (checkout v6.0.2, setup-python v6.2.0, upload-artifact v7.0.1)
- Add least-privilege permissions (contents: read) and concurrency cancellation
- Pin python-version to 3.12, pin runner to ubuntu-24.04
- Add pytest --timeout=300, --maxfail=99 (overrides pytest.ini -x), JUnit XML output
- Add explicit SSH setup with sshd start and connectivity preflight
- Hard-fail FUSE preflight (job stops if /dev/fuse or fusermount3 missing)
- Upload test results and meson logs as artifacts
- Add dependabot config for weekly action version updates
2026-05-19 19:55:13 -07:00
4 changed files with 54 additions and 239 deletions
+23 -83
View File
@@ -19,19 +19,9 @@ concurrency:
jobs:
build-and-test:
name: ${{ matrix.compiler }} / ${{ matrix.buildtype }}
name: Build and test
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
buildtype: [debugoptimized, release]
include:
- compiler: gcc
cc: gcc
- compiler: clang
cc: clang
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -44,15 +34,28 @@ jobs:
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc clang ninja-build libglib2.0-dev libfuse3-dev openssh-server openssh-client fuse3
sudo apt-get install -y valgrind gcc ninja-build libglib2.0-dev libfuse3-dev openssh-server openssh-client fuse3
- name: Check FUSE availability
run: |
test -e /dev/fuse
command -v fusermount3
- name: Install meson
run: pip3 install meson pytest pytest-timeout
- name: Print tool versions
- name: build
run: |
${{ matrix.cc }} --version
meson --version
meson setup build
ninja -C build
# cd does not persist across steps
- name: upload build artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sshfs
path: build/sshfs
if-no-files-found: ignore
- name: Setup SSH
run: |
@@ -64,80 +67,17 @@ jobs:
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: Build
env:
CC: ${{ matrix.cc }}
run: |
meson setup build --buildtype=${{ matrix.buildtype }} -Dwerror=true
ninja -C build
- name: Upload build artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sshfs-${{ matrix.compiler }}-${{ matrix.buildtype }}
path: build/sshfs
if-no-files-found: ignore
- name: Run tests
timeout-minutes: 20
- name: run tests
run: |
cd build
python3 -m pytest --maxfail=99 --timeout=300 --junitxml=test-results.xml test/
python3 -m pytest test/ --timeout=300 --junitxml=test-results.xml --maxfail=99
timeout-minutes: 20
- name: Upload test results
- name: upload test results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: test-results-${{ matrix.compiler }}-${{ matrix.buildtype }}
name: test-results
path: |
build/test-results.xml
build/meson-logs/
strict-warnings:
name: ${{ matrix.compiler }} / strict warnings
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- compiler: gcc
cc: gcc
extra_cflags: "-Wformat=2 -Wformat-security -Wundef -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wnull-dereference"
- compiler: clang
cc: clang
extra_cflags: "-Wformat=2 -Wformat-security -Wundef -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wnull-dereference"
steps:
- name: Checkout code
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 build dependencies
run: |
sudo apt-get update
sudo apt-get install -y gcc clang ninja-build libglib2.0-dev libfuse3-dev
- name: Install meson
run: pip3 install meson
- name: Print tool versions
run: |
${{ matrix.cc }} --version
meson --version
- name: Build with strict warnings
env:
CC: ${{ matrix.cc }}
CFLAGS: ${{ matrix.extra_cflags }}
run: |
meson setup build -Dwerror=true
ninja -C build
-51
View File
@@ -1,51 +0,0 @@
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
+31 -46
View File
@@ -280,11 +280,6 @@ struct read_chunk {
struct sshfs_io sio;
};
struct conntab_entry {
unsigned refcount;
struct conn *conn;
};
struct sshfs_file {
struct buffer handle;
struct list_head write_reqs;
@@ -294,11 +289,15 @@ struct sshfs_file {
off_t next_pos;
int is_seq;
struct conn *conn;
struct conntab_entry *ce;
int connver;
int modifver;
};
struct conntab_entry {
unsigned refcount;
struct conn *conn;
};
struct sshfs {
char *directport;
char *ssh_command;
@@ -1442,24 +1441,23 @@ static int do_read(struct conn *conn, struct buffer *buf)
static int sftp_read(struct conn *conn, uint8_t *type, struct buffer *buf)
{
int res = -1;
int res;
struct buffer buf2;
uint32_t len;
buf_init(&buf2, 5);
if (do_read(conn, &buf2) == -1)
goto out;
if (buf_get_uint32(&buf2, &len) == -1)
goto out;
if (len < 1 || len > MAX_REPLY_LEN) {
fprintf(stderr, "bad reply len: %u\n", len);
goto out;
res = do_read(conn, &buf2);
if (res != -1) {
if (buf_get_uint32(&buf2, &len) == -1)
return -1;
if (len > MAX_REPLY_LEN) {
fprintf(stderr, "reply len too large: %u\n", len);
return -1;
}
if (buf_get_uint8(&buf2, type) == -1)
return -1;
buf_init(buf, len - 1);
res = do_read(conn, buf);
}
if (buf_get_uint8(&buf2, type) == -1)
goto out;
buf_init(buf, len - 1);
res = do_read(conn, buf);
out:
buf_free(&buf2);
return res;
}
@@ -1532,14 +1530,10 @@ static int process_one_request(struct conn *conn)
buf_init(&buf, 0);
res = sftp_read(conn, &type, &buf);
if (res == -1) {
buf_free(&buf);
if (res == -1)
return -1;
}
if (buf_get_uint32(&buf, &id) == -1) {
buf_free(&buf);
if (buf_get_uint32(&buf, &id) == -1)
return -1;
}
pthread_mutex_lock(&sshfs.lock);
req = (struct request *)
@@ -2769,12 +2763,6 @@ static int sshfs_utimens(const char *path, const struct timespec tv[2],
return err;
}
static gboolean conntab_entry_is(gpointer key, gpointer value, gpointer data)
{
(void) key;
return value == data;
}
static int sshfs_open_common(const char *path, mode_t mode,
struct fuse_file_info *fi)
{
@@ -2835,13 +2823,12 @@ static int sshfs_open_common(const char *path, mode_t mode,
g_hash_table_insert(sshfs.conntab, g_strdup(path), ce);
}
sf->conn = ce->conn;
sf->ce = ce;
ce->refcount++;
sf->conn->file_count++;
assert(sf->conn->file_count > 0);
} else {
sf->conn = &sshfs.conns[0];
sf->ce = NULL;
ce = NULL; // only to silence compiler warning
}
sf->connver = sf->conn->connver;
pthread_mutex_unlock(&sshfs.lock);
@@ -2881,12 +2868,10 @@ static int sshfs_open_common(const char *path, mode_t mode,
if (sshfs.max_conns > 1) {
pthread_mutex_lock(&sshfs.lock);
sf->conn->file_count--;
sf->ce->refcount--;
if (sf->ce->refcount == 0) {
g_hash_table_foreach_remove(sshfs.conntab,
conntab_entry_is,
sf->ce);
g_free(sf->ce);
ce->refcount--;
if(ce->refcount == 0) {
g_hash_table_remove(sshfs.conntab, path);
g_free(ce);
}
pthread_mutex_unlock(&sshfs.lock);
}
@@ -2957,6 +2942,7 @@ static int sshfs_release(const char *path, struct fuse_file_info *fi)
{
struct sshfs_file *sf = get_sshfs_file(fi);
struct buffer *handle = &sf->handle;
struct conntab_entry *ce;
if (sshfs_file_is_conn(sf)) {
sshfs_flush(path, fi);
sftp_request(sf->conn, SSH_FXP_CLOSE, handle, 0, NULL);
@@ -2964,13 +2950,12 @@ static int sshfs_release(const char *path, struct fuse_file_info *fi)
buf_free(handle);
chunk_put_locked(sf->readahead);
if (sshfs.max_conns > 1) {
struct conntab_entry *ce = sf->ce;
pthread_mutex_lock(&sshfs.lock);
sf->conn->file_count--;
ce = g_hash_table_lookup(sshfs.conntab, path);
ce->refcount--;
if (ce->refcount == 0) {
g_hash_table_foreach_remove(sshfs.conntab,
conntab_entry_is, ce);
if(ce->refcount == 0) {
g_hash_table_remove(sshfs.conntab, path);
g_free(ce);
}
pthread_mutex_unlock(&sshfs.lock);
@@ -3397,7 +3382,7 @@ static int sshfs_statfs(const char *path, struct statvfs *buf)
return sshfs_ext_statvfs(path, buf);
buf->f_namemax = 255;
buf->f_bsize = sshfs.blksize ? sshfs.blksize : 4096;
buf->f_bsize = sshfs.blksize;
/*
* df seems to use f_bsize instead of f_frsize, so make them
* the same
@@ -3954,7 +3939,7 @@ static char *tokenize_on_space(char *str)
start = pos;
while (*pos != '\0') {
while (pos && *pos != '\0') {
// break on space, but not on '\ '
if (*pos == ' ' && *(pos - 1) != '\\') {
break;
-59
View File
@@ -168,7 +168,6 @@ def test_sshfs(
tst_mkdir_exist(mnt_dir)
tst_readdir_repeated(mnt_dir)
tst_rename_sibling(mnt_dir)
tst_rename_open_release(mnt_dir)
except Exception as exc:
cleanup(mount_process, mnt_dir)
raise exc
@@ -504,23 +503,6 @@ def tst_rename_sibling(mnt_dir):
os.unlink(name_c)
def tst_rename_open_release(mnt_dir):
src = pjoin(mnt_dir, name_generator())
dst = pjoin(mnt_dir, name_generator())
fd = os.open(src, os.O_CREAT | os.O_RDWR)
try:
os.write(fd, b"data")
os.rename(src, dst)
finally:
os.close(fd)
assert not os.path.exists(src)
with open(dst, "rb") as fh:
assert fh.read() == b"data"
os.unlink(dst)
def tst_link(mnt_dir, cache_timeout):
name1 = pjoin(mnt_dir, name_generator())
name2 = pjoin(mnt_dir, name_generator())
@@ -869,44 +851,3 @@ def test_direct_io(tmpdir, capfd):
raise
else:
umount(mount_process, mnt_dir)
def test_bad_sftp_reply_len(tmpdir):
"""sshfs must reject a zero-length SFTP reply instead of underflowing."""
helper = tmpdir.join("bad_sftp.py")
helper.write(
'#!/usr/bin/env python3\n'
'import os, struct, sys\n'
'def read_pkt():\n'
' hdr = os.read(0, 4)\n'
' if len(hdr) < 4: sys.exit(0)\n'
' n = struct.unpack(">I", hdr)[0]\n'
' while n:\n'
' c = os.read(0, n)\n'
' if not c: sys.exit(0)\n'
' n -= len(c)\n'
'read_pkt()\n'
'os.write(1, struct.pack(">IBI", 5, 2, 3))\n' # SSH_FXP_VERSION v3
'read_pkt()\n'
'os.write(1, struct.pack(">IB", 0, 0))\n' # len=0 reply (5 bytes on wire)
)
helper.chmod(0o755)
mnt_dir = str(tmpdir.mkdir("mnt"))
cmdline = base_cmdline + [
pjoin(basename, "sshfs"),
"-f",
"dummy:/",
mnt_dir,
"-o", f"ssh_command={helper}",
]
res = subprocess.run(
cmdline,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=10,
text=True,
)
assert res.returncode != 0
assert "bad reply len: 0" in res.stderr