Skip to content

Commit e5dc033

Browse files
authored
Merge pull request #31 from sysprog21/pre-commit
Overhaul pre-commit hook and CI checks
2 parents 6b9076a + 341cd08 commit e5dc033

File tree

18 files changed

+940
-194
lines changed

18 files changed

+940
-194
lines changed

.ci/check-cppcheck.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env bash
2+
3+
# Run cppcheck static analysis on kbox source files (src/ only).
4+
# Tests are excluded -- they use stubs and have different rules.
5+
#
6+
# CI mode: --max-configs=1 + --enable=warning for speed.
7+
# Deep analysis runs in the pre-commit hook on individual changed files.
8+
9+
set -e -u -o pipefail
10+
11+
# Exclude syscall-nr.c: it's a data table that causes cppcheck to hang
12+
# on older versions (>100s). The pre-commit hook checks it per-file.
13+
mapfile -t SOURCES < <(git ls-files -z -- 'src/*.c' | tr '\0' '\n' | grep -v 'syscall-nr\.c')
14+
15+
if [ ${#SOURCES[@]} -eq 0 ]; then
16+
echo "No tracked C source files found."
17+
exit 0
18+
fi
19+
20+
# Run cppcheck with a timeout to prevent CI hangs.
21+
# 120s is generous -- this should finish in <30s with --max-configs=1.
22+
timeout 120 cppcheck \
23+
-I. -Iinclude -Isrc \
24+
--platform=unix64 \
25+
--enable=warning \
26+
--max-configs=1 --error-exitcode=1 --inline-suppr \
27+
--suppress=checkersReport --suppress=unmatchedSuppression \
28+
--suppress=missingIncludeSystem --suppress=noValidConfiguration \
29+
--suppress=normalCheckLevelMaxBranches \
30+
--suppress=preprocessorErrorDirective \
31+
-D_GNU_SOURCE -D__linux__ \
32+
"${SOURCES[@]}"

.ci/check-format.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env bash
2+
3+
# Verify clang-format-20 conformance for all tracked C/H files.
4+
5+
# Require clang-format-20 -- older versions produce different output.
6+
if [ -z "${CLANG_FORMAT:-}" ]; then
7+
if command -v clang-format-20 >/dev/null 2>&1; then
8+
CLANG_FORMAT="clang-format-20"
9+
else
10+
echo "Error: clang-format-20 is required (older versions differ in style)" >&2
11+
exit 1
12+
fi
13+
fi
14+
15+
ret=0
16+
while IFS= read -r -d '' file; do
17+
expected=$(mktemp)
18+
"$CLANG_FORMAT" "$file" >"$expected" 2>/dev/null
19+
if ! diff -u -p --label="$file" --label="expected coding style" "$file" "$expected"; then
20+
ret=1
21+
fi
22+
rm -f "$expected"
23+
done < <(git ls-files -z -- '*.c' '*.h')
24+
25+
exit $ret

.ci/check-newline.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
3+
# Ensure all tracked C/H files end with a newline.
4+
5+
set -e -u -o pipefail
6+
7+
ret=0
8+
while IFS= read -rd '' f; do
9+
if file --mime-encoding "$f" | grep -qv binary; then
10+
if [ -n "$(tail -c1 < "$f")" ]; then
11+
echo "Warning: No newline at end of file $f"
12+
ret=1
13+
fi
14+
fi
15+
done < <(git ls-files -z -- '*.c' '*.h')
16+
17+
exit $ret

.ci/check-security.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
3+
# Security checks for kbox source files (src/ and include/ only).
4+
# Tests and guest/stress binaries are excluded -- they have different rules.
5+
#
6+
# 1. Banned functions -- unsafe libc calls with safer alternatives.
7+
# 2. Credential / secret patterns -- catch accidental key leaks.
8+
# 3. Dangerous preprocessor -- detect disabled security features.
9+
10+
set -u -o pipefail
11+
12+
failed=0
13+
14+
# --- Patterns ---
15+
banned='(^|[^[:alnum:]_])(gets|sprintf|vsprintf|strcpy|stpcpy|strcat|atoi|atol|atoll|atof|mktemp|tmpnam|tempnam)[[:space:]]*\('
16+
secrets='(password|secret|api_key|private_key|token)[[:space:]]*=[[:space:]]*"[^"]+'
17+
dangerous_pp='#[[:space:]]*(undef|define)[[:space:]]+((_FORTIFY_SOURCE[[:space:]]+0)|(__SSP__))'
18+
comment_only='^[[:space:]]*(//|/\*|\*|\*/)'
19+
20+
# Only scan kbox source, not tests/guest/stress.
21+
while IFS= read -r -d '' f; do
22+
# Skip comment-only matches before checking.
23+
code=$(grep -vE "$comment_only" "$f")
24+
25+
if echo "$code" | grep -qE "$banned"; then
26+
echo "Banned function in $f:"
27+
grep -nE "$banned" "$f" | grep -vE "$comment_only"
28+
failed=1
29+
fi
30+
if echo "$code" | grep -iqE "$secrets"; then
31+
echo "Possible hardcoded secret in $f:"
32+
grep -inE "$secrets" "$f" | grep -vE "$comment_only"
33+
failed=1
34+
fi
35+
if echo "$code" | grep -qE "$dangerous_pp"; then
36+
echo "Dangerous preprocessor directive in $f:"
37+
grep -nE "$dangerous_pp" "$f" | grep -vE "$comment_only"
38+
failed=1
39+
fi
40+
done < <(git ls-files -z -- 'src/*.c' 'src/*.h' 'include/*.h' 'include/**/*.h')
41+
42+
if [ $failed -eq 0 ]; then
43+
echo "Security checks passed."
44+
fi
45+
46+
exit $failed

.editorconfig

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
1-
# Top-level EditorConfig file
21
root = true
32

4-
# Shell script-specific settings
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
8+
[Makefile]
9+
indent_style = tab
10+
indent_size = 4
11+
12+
[mk/*]
13+
indent_style = tab
14+
indent_size = 4
15+
16+
[*.[ch]]
17+
indent_style = space
18+
indent_size = 4
19+
max_line_length = 80
20+
21+
[*.py]
22+
indent_style = space
23+
indent_size = 4
24+
525
[*.sh]
626
indent_style = space
727
indent_size = 4
8-
end_of_line = lf
9-
trim_trailing_whitespace = true
10-
insert_final_newline = true
11-
function_next_line = true
12-
switch_case_indent = true
13-
space_redirects = true
14-
binary_next_line = true
28+
29+
[*.hook]
30+
indent_style = space
31+
indent_size = 4
32+
33+
[*.yml]
34+
indent_style = space
35+
indent_size = 2
36+
# test
37+
# test
38+
# test
39+
# test
40+
# x

.github/workflows/build-kbox.yml

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# Build kbox and run full test suite.
22
# Zero root required -- everything runs as an unprivileged user.
33
#
4-
# Parallelism:
5-
# unit-tests -- no LKL, no rootfs, runs immediately
6-
# build-kbox -- fetches LKL, compiles kbox + guest/stress bins, builds rootfs
7-
# integration -- needs build-kbox artifacts, runs integration + stress tests
4+
# Parallelism (5 independent jobs, 1 sequential):
5+
# coding-style -- clang-format + newline checks (fast)
6+
# static-analysis -- security patterns + cppcheck
7+
# unit-tests -- no LKL dependency, ASAN/UBSAN
8+
# build-kbox -- fetches LKL, compiles kbox + guest/stress bins, builds rootfs
9+
# integration -- needs build-kbox artifacts, runs integration + stress tests
810
#
9-
# unit-tests and build-kbox run in parallel. integration waits for build-kbox.
11+
# coding-style, static-analysis, unit-tests, and build-kbox run in parallel.
12+
# integration-tests waits for build-kbox only.
1013
name: Build and Test
1114

1215
on:
@@ -16,6 +19,42 @@ on:
1619
branches: [main]
1720

1821
jobs:
22+
# ---- Coding style: formatting checks (fast, ~10s) ----
23+
coding-style:
24+
runs-on: ubuntu-24.04
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v6
28+
29+
- name: Install clang-format
30+
run: |
31+
sudo apt-get update
32+
sudo apt-get install -y clang-format-20
33+
34+
- name: Check trailing newline
35+
run: .ci/check-newline.sh
36+
37+
- name: Check clang-format
38+
run: .ci/check-format.sh
39+
40+
# ---- Static analysis: security patterns + cppcheck ----
41+
static-analysis:
42+
runs-on: ubuntu-24.04
43+
steps:
44+
- name: Checkout
45+
uses: actions/checkout@v6
46+
47+
- name: Install cppcheck
48+
run: |
49+
sudo apt-get update
50+
sudo apt-get install -y cppcheck
51+
52+
- name: Security checks
53+
run: .ci/check-security.sh
54+
55+
- name: Static analysis (cppcheck)
56+
run: .ci/check-cppcheck.sh
57+
1958
# ---- Unit tests: no LKL dependency, fast ----
2059
unit-tests:
2160
runs-on: ubuntu-24.04

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ KCONFIG_CONF := configs/Kconfig
1515

1616
# Targets that don't require .config
1717
CONFIG_TARGETS := config defconfig oldconfig savedefconfig clean distclean indent \
18-
check-unit fetch-lkl build-lkl fetch-minislirp install-hooks \
19-
guest-bins stress-bins rootfs
18+
check-unit check-syntax fetch-lkl build-lkl fetch-minislirp \
19+
install-hooks guest-bins stress-bins rootfs
2020
CONFIG_GENERATORS := config defconfig oldconfig
2121

2222
# Require .config for build targets.

mk/tests.mk

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ TEST_DIR = tests/unit
66
TEST_SRCS = $(TEST_DIR)/test-runner.c \
77
$(TEST_DIR)/test-fd-table.c \
88
$(TEST_DIR)/test-path.c \
9+
$(TEST_DIR)/test-cli.c \
910
$(TEST_DIR)/test-identity.c \
1011
$(TEST_DIR)/test-syscall-nr.c \
1112
$(TEST_DIR)/test-elf.c \
@@ -29,6 +30,7 @@ endif
2930
# Unit tests link only the pure-computation sources (no LKL)
3031
TEST_SUPPORT_SRCS = $(SRC_DIR)/fd-table.c \
3132
$(SRC_DIR)/path.c \
33+
$(SRC_DIR)/cli.c \
3234
$(SRC_DIR)/identity.c \
3335
$(SRC_DIR)/syscall-nr.c \
3436
$(SRC_DIR)/elf.c \
@@ -111,4 +113,45 @@ $(ROOTFS): scripts/mkrootfs.sh scripts/alpine-sha256.txt $(GUEST_BINS) $(STRESS_
111113
@echo " GEN $@"
112114
$(Q)ALPINE_ARCH=$(ARCH) ./scripts/mkrootfs.sh
113115

114-
.PHONY: check check-unit check-integration check-stress guest-bins stress-bins rootfs
116+
# ---- Syntax-only compilation check (used by pre-commit hook) ----
117+
# Usage: make check-syntax CHK_SOURCES="src/foo.c src/bar.c"
118+
# Uses the project's real CFLAGS with -fsyntax-only (no linking, no .o output).
119+
# Skips gracefully on non-Linux compilers.
120+
121+
CHK_CC_TARGET := $(shell $(CC) -dumpmachine 2>/dev/null)
122+
check-syntax:
123+
ifeq ($(findstring linux,$(CHK_CC_TARGET)),)
124+
@echo " SKIP check-syntax (non-Linux compiler: $(CHK_CC_TARGET))"
125+
else
126+
ifdef CHK_SOURCES
127+
@echo " SYNTAX $(words $(CHK_SOURCES)) file(s)"
128+
$(Q)$(CC) $(CFLAGS) -DKBOX_UNIT_TEST -fsyntax-only \
129+
-Werror=implicit-function-declaration \
130+
-Werror=incompatible-pointer-types \
131+
-Werror=int-conversion \
132+
-Werror=return-type \
133+
-Werror=format=2 \
134+
-Werror=format-security \
135+
-Werror=strict-prototypes \
136+
-Werror=old-style-definition \
137+
-Werror=sizeof-pointer-memaccess \
138+
-Werror=vla \
139+
$(CHK_SOURCES)
140+
else
141+
@echo " SYNTAX all source files"
142+
$(Q)$(CC) $(CFLAGS) -DKBOX_UNIT_TEST -fsyntax-only \
143+
-Werror=implicit-function-declaration \
144+
-Werror=incompatible-pointer-types \
145+
-Werror=int-conversion \
146+
-Werror=return-type \
147+
-Werror=format=2 \
148+
-Werror=format-security \
149+
-Werror=strict-prototypes \
150+
-Werror=old-style-definition \
151+
-Werror=sizeof-pointer-memaccess \
152+
-Werror=vla \
153+
$(SRCS)
154+
endif
155+
endif
156+
157+
.PHONY: check check-unit check-integration check-stress guest-bins stress-bins rootfs check-syntax

0 commit comments

Comments
 (0)