Repository navigation
391 lines (367 loc) · 20.2 KB
/
Copy pathLinux-samples-network.yml
File metadata and controls
391 lines (367 loc) · 20.2 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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
name: CI Linux samples/network
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
permissions:
contents: read
env:
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
samples-network:
name: Build and run samples/network
runs-on: ubuntu-latest
env:
APT_PKGS: build-essential libmbedtls-dev libbearssl-dev ccache
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Apt cache key
id: apt
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
- name: Restore apt cache
uses: actions/cache/restore@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
restore-keys: ${{ steps.apt.outputs.prefix }}
- name: Install dependencies
timeout-minutes: 20
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
- name: Save apt cache
if: env.APT_CACHE_SAVE == '1'
uses: actions/cache/save@v4
with:
path: ~/.apt-cache
key: ${{ steps.apt.outputs.key }}
- name: Link with lld
run: sh .github/workflows/scripts/use-lld.sh
- name: Restore ccache
uses: actions/cache/restore@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
restore-keys: |
ccache-${{ github.workflow }}-${{ github.job }}-
- name: Enable ccache
run: |
set -eu
export CCACHE_DIR="$HOME/.ccache"
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
ccache -M 250M
ccache -z
- name: Build and run webdav_options_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/webdav_options
run: |
set -eu
# Includes network/cloud_sync/webdav.c and drives the OPTIONS
# capability probe as shipped over real-world header sets. The
# probe has to separate a WebDAV server from a plain HTTP
# server that answers OPTIONS, and the target it probes is a
# collection, on which PUT is not defined (RFC 4918 9.7.1) -
# so PUT's absence from Allow must not fail it. The previous
# probe did exactly that, rejecting mod_dav and Koofr
# (libretro/RetroArch#19486) while accepting a plain nginx
# that happened to list PUT. Covers the DAV header, Allow-only
# WebDAV methods, HTTP/2 lowercase field names, plain HTTP
# with and without PUT, empty and NULL header lists, and
# whole-token matching (PROPFINDX must not match PROPFIND).
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_options_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_options_test
echo "[pass] webdav_options_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_options_test
echo "[pass] webdav_options_test (TSan)"
- name: Build and run webdav_reauth_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/webdav_reauth
run: |
set -eu
# Includes network/cloud_sync/webdav.c and plays the server
# for every request type the driver sends (OPTIONS, GET, PUT,
# DELETE, MOVE, MKCOL). A server that keeps answering 401 with
# a Digest challenge - a wrong password - must get one retry
# and then a reported failure; the driver used to retry every
# challenge, so a mistyped password looped the first request
# of a sync forever. A single stale-nonce challenge must still
# be retried and succeed. A retried PUT must send the file:
# it used to re-read it from the end the first attempt left
# it at and upload uninitialised memory in place of the save.
# A malformed challenge (a value with no closing quote, a
# trailing unquoted parameter) must be refused, not crash the
# parser or send it reading past the end of the line.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_reauth_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_reauth_test
echo "[pass] webdav_reauth_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_reauth_test
echo "[pass] webdav_reauth_test (TSan)"
- name: Build and run webdav_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/webdav_backup
run: |
set -eu
# Includes network/cloud_sync/webdav.c and plays the server,
# request by request. With destructive sync off, an upload
# must COPY the server's file to deleted/<path>-<time> before
# the PUT replaces it - it used to overwrite it in place, so
# the setting protected against deletes but not overwrites.
# Covers the order (backup first, into an existing deleted/
# collection, named as a delete names it), 404 as "nothing to
# back up", a failed backup stopping the upload, destructive
# sync and the server manifest skipping the backup, and COPY
# and PUT each getting their own Digest retry.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x webdav_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./webdav_backup_test
echo "[pass] webdav_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_backup_test
echo "[pass] webdav_backup_test (TSan)"
- name: Build and run s3_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/s3_backup
run: |
set -eu
# Includes network/cloud_sync/s3.c and plays the server,
# request by request. The S3 driver never read the
# destructive sync setting: deletes removed objects for good
# and uploads overwrote them. With it off, both must first
# CopyObject the object to deleted/<path>-<time>. Covers the
# copy source for virtual-hosted and path-style endpoints,
# x-amz-copy-source being signed, 404 as "nothing to keep", a
# failed copy (including a 200 with an <Error> body) stopping
# the delete or upload, and destructive sync and the server
# manifest skipping the copy.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x s3_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./s3_backup_test
echo "[pass] s3_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./s3_backup_test
echo "[pass] s3_backup_test (TSan)"
- name: Build and run smb_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/smb_backup
run: |
set -eu
# Includes network/cloud_sync/smb.c and runs it against an
# in-memory libsmb2 share (fake_smb.c), checking what the
# share holds after each upload. The driver used to truncate
# the server file and then write, so a write failing part way
# left a truncated save; and with destructive sync off it
# overwrote the file without keeping the old copy a delete
# keeps. Uploads now go through <path>.rauploading and the
# replaced file is kept as <path>-<time> (non-destructive).
# Covers a failed write, backup and final rename each leaving
# the old file intact with no temporary file left, no backup
# for destructive sync, a new file or the manifest, an empty
# upload and the configured subdirectory.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x smb_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./smb_backup_test
echo "[pass] smb_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./smb_backup_test
echo "[pass] smb_backup_test (TSan)"
- name: Build and run nfs_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/nfs_backup
run: |
set -eu
# Includes network/cloud_sync/nfs.c and runs it against an
# in-memory export (fake_nfs.c) keeping net_nfs3.c's status
# contract. A download is written beside the local file and
# renamed over it once complete; only the server answering
# NOENT reads as "not there"; an upload is one RENAME over the
# old file, kept as <path>-<time>[-n] with destructive sync
# off. Covers failed opens and partial reads leaving the local
# save intact, unanswered stat/unlink failing instead of
# reporting a missing file or a delete, failed write, backup
# and final rename leaving the old file and no temporary, the
# manifest skipping the backup, refused over-long names, the
# subdirectory and a concurrent MKDIR.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x nfs_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./nfs_backup_test
echo "[pass] nfs_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./nfs_backup_test
echo "[pass] nfs_backup_test (TSan)"
- name: Build and run gdrive_backup_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/gdrive_backup
run: |
set -eu
# Includes network/cloud_sync/google_drive.c and plays the
# Drive API, request by request. With destructive sync off, a
# delete kept the old file (renamed <name>-<time>) but an
# upload replaced its content outright. An upload to an
# existing file must now files.copy it to that name in the
# same folder before the PATCH. Covers the order and the copy
# request, a failed copy stopping the upload, a 401 on the
# copy and on the PATCH each refreshing the token once (the
# PATCH retry repeating only the PATCH, not the backup), and
# destructive sync, a new file and the manifest skipping it.
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x gdrive_backup_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
./gdrive_backup_test
echo "[pass] gdrive_backup_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./gdrive_backup_test
echo "[pass] gdrive_backup_test (TSan)"
- name: Build and run cheevos_login_test (bear ASan+UBSan, bear TSan, mbed ASan+UBSan)
shell: bash
working-directory: samples/network/cheevos_login
run: |
set -eu
# Regression guard for the RetroAchievements login transport
# (libretro/RetroArch#19562, #19457): drives net_http with the
# exact login shape cheevos_client.c produces, against a local
# mock server that validates every request byte-for-byte, over
# plain HTTP and over TLS with the real SSL backend linked in.
# Covers: login on a fresh TLS connection, trust-store loading
# from a realistic messy CA bundle (BearSSL), keep-alive pool
# reuse, one-shot replay of a request on a pooled connection
# the server closed while idle, and no replay once response
# bytes have been seen. Each TLS backend's non-blocking send
# must push a 12 MiB body past a server that stops reading for
# a second without ever blocking, and deliver every byte; and
# net_http must send the same upload a slice per update, plain
# and over TLS - it used to go out in one blocking send, a call
# of a second or more (spinning at 100% CPU on a plain socket).
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
test -x cheevos_login_test
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check
echo "[pass] cheevos_login_test (BearSSL, ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 180 make check
echo "[pass] cheevos_login_test (BearSSL, TSan)"
make clean SSL_BACKEND=mbed SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=mbed SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=mbed
echo "[pass] cheevos_login_test (mbedTLS, ASan)"
# The built-in client, RetroArch's default backend.
make clean SSL_BACKEND=retro SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=retro SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=retro
echo "[pass] cheevos_login_test (built-in TLS, ASan)"
# The runner's libmbedtls-dev is 2.28, which has no TLS 1.3,
# so the leg above cannot see either TLS 1.3 failure. Mbed
# TLS 3.6.0 is the one release that shows both: it fails
# every handshake unless psa_crypto_init() was called, and it
# reports session tickets from mbedtls_ssl_read(). Library
# only, ~20 s to build. A download that still fails after
# the retries skips the leg rather than failing the job.
mbed_ver=3.6.0
mbed_sha=3ecf94fcfdaacafb757786a01b7538a61750ebd85c4b024f56ff8ba1490fcd38
mbed_tar="$RUNNER_TEMP/mbedtls-$mbed_ver.tar.bz2"
if curl -fsSL --retry 3 --retry-delay 5 --max-time 120 -o "$mbed_tar" \
"https://github.com/Mbed-TLS/mbedtls/releases/download/v$mbed_ver/mbedtls-$mbed_ver.tar.bz2"; then
echo "$mbed_sha $mbed_tar" | sha256sum -c -
tar -xjf "$mbed_tar" -C "$RUNNER_TEMP"
make -C "$RUNNER_TEMP/mbedtls-$mbed_ver" -j"$(nproc)" lib >/dev/null
make clean SSL_BACKEND=mbed SANITIZER=address,undefined && make -j$(nproc) all SSL_BACKEND=mbed SANITIZER=address,undefined \
MBEDTLS_DIR="$RUNNER_TEMP/mbedtls-$mbed_ver"
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
make check SSL_BACKEND=mbed
echo "[pass] cheevos_login_test (mbedTLS $mbed_ver TLS 1.3, ASan)"
else
echo "::warning::Mbed TLS $mbed_ver download failed; TLS 1.3 leg skipped"
fi
- name: Build and run net_http_semantics_test (ASan + UBSan, TSan)
shell: bash
working-directory: samples/network/net_http_semantics
run: |
set -eu
# HTTP/1.1 semantics of libretro-common/net/net_http.c against
# an in-process scripted server on 127.0.0.1, which logs which
# connection every request arrived on. Covers GET 200 framing
# and pool reuse, and responses without a body: HEAD, 204 and
# 304 on keep-alive used to sit waiting for a close that never
# came, a 1xx other than 100 was taken as the final status,
# and a 101 or stray bytes after a bodyless response left the
# socket in the pool. Connection and Transfer-Encoding are
# read as lists ("Keep-Alive, Close", "gzip, chunked"),
# HTTP/1.0 without keep-alive is not pooled, and a length
# after "chunked" does not reframe the body. Redirects: an
# absolute-path Location used to go out as "GET //foo", a
# 302 loop was followed forever (now 8 hops), a streamed PUT
# redirected by a 307 is rewound and resent whole, and a 3xx
# body no longer lands in the caller's sink. Bounds: a header
# block (1xx blocks included) over 64 KiB and a chunk size
# with no hex digits fail instead of growing the buffer or
# passing as the last chunk, a Content-Length buffer never
# grows past the body, and a pooled connection that dies
# after the status line is not replayed. A URL shorter than
# its scheme is rejected without an overread. Unit matrix for
# net_http_url_join() and net_http_urldecode(), including
# buffer bounds and in-place decoding; redirects resolved
# through it ("../", "?q", "//host", fragments dropped,
# %20 left encoded). The response header block: lookup and
# iteration, the legacy string_list accessor, and 64 header
# lines costing no per-line allocations. A Content-Length or
# "chunked" on a 1xx block does not frame the final response.
# A followed 3xx's body is dropped as it arrives, not
# buffered to its Content-Length, and bytes past a body's
# Content-Length end the connection, not the transfer.
# Every case has a
# deadline, so a hang fails the case instead of the job.
# net_http is lock-free by design (DNS cache and pool owned by
# the driving thread, resolvers publish with an atomic store);
# keep it that way.
if grep -nE '\b(slock|scond)_[a-z_]+\(' ../../../libretro-common/net/net_http.c; then
echo "::error title=net_http lock::net_http.c must not take locks"
exit 1
fi
make clean SANITIZER=address,undefined && make -j$(nproc) all SANITIZER=address,undefined
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
./net_http_semantics_test
echo "[pass] net_http_semantics_test (ASan)"
make clean SANITIZER=thread && make -j$(nproc) all SANITIZER=thread
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./net_http_semantics_test
echo "[pass] net_http_semantics_test (TSan)"
- name: ccache statistics
if: ${{ !cancelled() }}
continue-on-error: true
run: ccache -sv
- name: Save ccache
# Only master's pushes keep a cache: pull requests restore
# master's, so one push does not evict the next one's.
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
path: ~/.ccache
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}