Repository navigation
295 lines (255 loc) · 11.1 KB
/
Copy pathvalidate-kernel-commits.yml
File metadata and controls
295 lines (255 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
name: Validate Kernel Commits
on:
workflow_call:
# No inputs needed - uses github context from caller
permissions:
contents: read
# No pull-requests: write needed - we don't comment here
jobs:
validate-kernel-commits-check:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Validate and sanitize inputs
id: validate_inputs
env:
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_COMMITS: ${{ github.event.pull_request.commits }}
run: |
# Validate base branch name (alphanumeric, dots, slashes, dashes, underscores, curly braces)
# Note: hyphen must be at end of character class or escaped to be literal
if ! [[ "$BASE_REF" =~ ^[a-zA-Z0-9/_.{}-]+$ ]]; then
echo "❌ Invalid base branch name: $BASE_REF"
exit 1
fi
# Validate head branch name
if ! [[ "$HEAD_REF" =~ ^[a-zA-Z0-9/_.{}-]+$ ]]; then
echo "❌ Invalid head branch name: $HEAD_REF"
exit 1
fi
# Validate length (prevent resource exhaustion)
if [ ${#BASE_REF} -gt 255 ]; then
echo "❌ Base branch name too long"
exit 1
fi
if [ ${#HEAD_REF} -gt 255 ]; then
echo "❌ Head branch name too long"
exit 1
fi
# Validate PR number is numeric
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "❌ Invalid PR number: $PR_NUMBER"
exit 1
fi
# Validate commits count is numeric
if ! [[ "$PR_COMMITS" =~ ^[0-9]+$ ]]; then
echo "❌ Invalid commits count: $PR_COMMITS"
exit 1
fi
# Pass validated values to environment
echo "BASE_REF=$BASE_REF" >> "$GITHUB_ENV"
echo "HEAD_REF=$HEAD_REF" >> "$GITHUB_ENV"
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_ENV"
echo "PR_COMMITS=$PR_COMMITS" >> "$GITHUB_ENV"
- name: Clone base branch
env:
BASE_CLONE_URL: ${{ github.event.pull_request.base.repo.clone_url }}
run: |
# Use environment variables to prevent injection
git clone --depth=1 --no-checkout "$BASE_CLONE_URL" -b "$BASE_REF" .
- name: Fetch PR branch
env:
HEAD_CLONE_URL: ${{ github.event.pull_request.head.repo.clone_url }}
run: |
# Use environment variables to prevent command injection
git fetch --depth=$((PR_COMMITS + 1)) "$HEAD_CLONE_URL" "$HEAD_REF"
HEAD_SHA=$(git rev-parse FETCH_HEAD)
# Validate SHA format (40 hex characters)
if ! [[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "❌ Invalid SHA format: $HEAD_SHA"
exit 1
fi
echo "HEAD_SHA=$HEAD_SHA" >> "$GITHUB_ENV"
- name: Verify PR branch isn't on stale base
run: |
if ! git merge-base --is-ancestor "$BASE_REF" "$HEAD_SHA"; then
echo "❌ PR branch must be rebased onto latest base branch commit"
exit 1
fi
- name: Fetch upstream mainline branch
run: |
# Determine the kernel version tag, since it is the most recent common
# ancestor between the base branch and the upstream mainline branch
MAKEFILE=$(git show "$BASE_REF":Makefile)
VERSION=$(grep -m1 -Po '(?<=^VERSION = )\d+' <<< "$MAKEFILE")
PATCHLEVEL=$(grep -m1 -Po '(?<=^PATCHLEVEL = )\d+' <<< "$MAKEFILE")
# Validate VERSION and PATCHLEVEL are numeric
if ! [[ "$VERSION" =~ ^[0-9]+$ ]] || ! [[ "$PATCHLEVEL" =~ ^[0-9]+$ ]]; then
echo "❌ Invalid kernel version: $VERSION.$PATCHLEVEL"
exit 1
fi
# Fetch upstream mainline branch without tags, fetching only history
# that is newer than this kernel version tag. This reduces the amount
# of commits cloned knowing that there's no need to check any commits
# for Fixes commits older than what the base branch already contains
git fetch --no-tags --shallow-exclude="v$VERSION.$PATCHLEVEL" origin kernel-mainline
MAINLINE_SHA=$(git rev-parse FETCH_HEAD)
# Validate SHA format
if ! [[ "$MAINLINE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "❌ Invalid mainline SHA format: $MAINLINE_SHA"
exit 1
fi
echo "MAINLINE_SHA=$MAINLINE_SHA" >> "$GITHUB_ENV"
- name: Checkout kernel-src-tree-tools
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: ctrliq/kernel-src-tree-tools
ref: 'mainline'
path: kernel-src-tree-tools
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.x'
- name: Install Python dependencies
working-directory: kernel-src-tree-tools
run: |
python3 -m pip install --upgrade pip
pip install .
- name: Run upstream fixes check
id: check-kernel-commits
working-directory: kernel-src-tree-tools
run: |
set +e # Don't exit on error, we want to capture the output
set -o pipefail # Capture exit code from python script, not tee
python3 check_kernel_commits.py \
--repo .. \
--pr_branch "$HEAD_SHA" \
--base_branch "$BASE_REF" \
--markdown \
--upstream-ref "$MAINLINE_SHA" \
--check-cves | tee ../ckc_result.txt
EXIT_CODE=$?
# Check if the script failed
if [ $EXIT_CODE -ne 0 ]; then
echo "❌ Kernel commits check failed with exit code $EXIT_CODE"
exit $EXIT_CODE
fi
# Check for findings:
# 1. Verify the success message exists
# 2. If it exists, check if there are any OTHER lines (which would indicate issues)
# 3. If success message doesn't exist, that's also a finding
if grep -q "All referenced commits exist upstream and have no Fixes: tags." ../ckc_result.txt; then
# Success message found, check if there are any other lines
LINE_COUNT=$(wc -l < ../ckc_result.txt)
if [ "$LINE_COUNT" -gt 1 ]; then
echo "has_findings=true" >> $GITHUB_OUTPUT
else
echo "has_findings=false" >> $GITHUB_OUTPUT
fi
else
# Success message not found, there must be findings
echo "has_findings=true" >> $GITHUB_OUTPUT
fi
set -e # Re-enable exit on error
- name: Save PR metadata for comment workflow
env:
HEAD_REPO_FULL_NAME: ${{ github.event.pull_request.head.repo.full_name }}
REPOSITORY: ${{ github.repository }}
run: |
mkdir -p pr_metadata
# Save validated metadata
echo "$PR_NUMBER" > pr_metadata/pr_number.txt
echo "$REPOSITORY" > pr_metadata/repository.txt
echo "$BASE_REF" > pr_metadata/base_ref.txt
echo "$HEAD_SHA" > pr_metadata/head_sha.txt
echo "$HEAD_REPO_FULL_NAME" > pr_metadata/head_repo.txt
# Create a checksum of metadata for integrity verification
(cd pr_metadata && sha256sum *.txt > checksums.txt)
- name: Upload check results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always() # Upload even if checks fail
with:
name: check-results
path: |
ckc_result.txt
pr_metadata/
retention-days: 3 # Increased from 1 (then 3) to prevent premature deletion and support manual follow-ups
compare-backports:
runs-on: ubuntu-24.04
timeout-minutes: 120
permissions:
contents: read
pull-requests: read
steps:
- name: Check out the workflow scripts
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Kernel branches call this workflow from main, so take the scripts
# from the commit that defines this job rather than from the PR
repository: ${{ job.workflow_repository }}
ref: ${{ job.workflow_sha }}
sparse-checkout: .github/scripts
path: workflow-scripts
persist-credentials: false
- name: Check out libgit2
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: libgit2/libgit2
ref: 0060d9cf5666f015b1067129bd874c6cc4c9c7ac # v1.9.1
path: libgit2
persist-credentials: false
- name: Check out diffofdiffs
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: ctrliq/diffofdiffs
ref: 0ee3188313eb76722387b81d8852da207926ff77 # v1.1.0
path: diffofdiffs
persist-credentials: false
- name: Build diffofdiffs
run: |
# diffofdiffs needs libgit2 1.9.1 or newer, which Ubuntu 24.04
# doesn't package
PREFIX="$RUNNER_TEMP/libgit2"
cmake -S libgit2 -B libgit2/build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DCMAKE_INSTALL_LIBDIR=lib \
-DBUILD_TESTS=OFF -DBUILD_CLI=OFF \
-DUSE_SSH=OFF -DUSE_HTTPS=OFF \
-DREGEX_BACKEND=builtin -DUSE_BUNDLED_ZLIB=ON
cmake --build libgit2/build --parallel "$(nproc)"
cmake --install libgit2/build
make -C diffofdiffs -j"$(nproc)" \
CPPFLAGS="-I$PREFIX/include" \
LDFLAGS="-L$PREFIX/lib -Wl,-rpath,$PREFIX/lib"
- name: Compare backports with their upstream commits
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_COMMITS: ${{ github.event.pull_request.commits }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_CLONE_URL: ${{ github.event.pull_request.head.repo.clone_url }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
DIFFOFDIFFS: ${{ github.workspace }}/diffofdiffs/diffofdiffs
OUTPUT_DIR: ${{ github.workspace }}/comparisons
run: python3 -I workflow-scripts/.github/scripts/diffofdiffs-compare.py
- name: Upload the HTML report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
# An unarchived upload keeps the file name as the artifact name and
# lets GitHub show the report in the browser
path: comparisons/diffofdiffs-report-${{ github.run_attempt }}.html
archive: false
if-no-files-found: ignore
retention-days: 90
- name: Upload the comparison results
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: diffofdiffs-results-${{ github.run_attempt }}
path: comparisons/comparisons.json
if-no-files-found: error
retention-days: 3