Skip to content

Commit b829b47

Browse files
committed
ci: test example modules with AddressSanitizer
This requires a certain degree of compatibility between the Rust and C toolchains, so we use AlmaLinux packages of clang and rustc that are built against the same LLVM and are known to be compatible. This also requires unstable compiler options, but `RUSTC_BOOTSTRAP=1` can be used to force enable nightly features on stable toolchain.
1 parent 6066bfd commit b829b47

File tree

4 files changed

+183
-0
lines changed

4 files changed

+183
-0
lines changed

.github/workflows/sanitizers.yaml

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
name: sanitizers
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
9+
env:
10+
CARGO_TERM_COLOR: 'always'
11+
RUST_BACKTRACE: '1'
12+
BUILDREQUIRES: >-
13+
openssl-devel pcre2-devel zlib-devel
14+
cargo rust-src rustfmt
15+
clang compiler-rt
16+
git-core
17+
make patch
18+
perl-FindBin
19+
perl-IO-Socket-SSL
20+
perl-Test-Harness
21+
perl-Test-Simple
22+
perl-lib
23+
24+
jobs:
25+
test:
26+
runs-on: ubuntu-latest
27+
container: ghcr.io/almalinux/almalinux:10-kitten
28+
29+
strategy:
30+
fail-fast: false
31+
matrix:
32+
nginx-ref:
33+
# master
34+
- stable-1.28
35+
36+
steps:
37+
- name: Install dependencies
38+
run: |
39+
dnf install -y 'dnf-command(config-manager)' epel-release
40+
dnf config-manager -y --set-enabled crb epel
41+
dnf install -y ${BUILDREQUIRES}
42+
43+
- uses: actions/checkout@v4
44+
- uses: actions/checkout@v4
45+
with:
46+
ref: ${{ matrix.nginx-ref }}
47+
repository: 'nginx/nginx'
48+
path: 'nginx'
49+
- uses: actions/checkout@v4
50+
with:
51+
repository: 'nginx/nginx-tests'
52+
path: 'nginx/tests'
53+
54+
- uses: actions/cache@v4
55+
with:
56+
path: |
57+
~/.cargo/bin/
58+
~/.cargo/registry/index/
59+
~/.cargo/registry/cache/
60+
~/.cargo/git/db/
61+
nginx/objs/ngx_rust_examples
62+
key: ${{ runner.os }}-cargo-asan-${{ hashFiles('**/Cargo.lock') }}
63+
restore-keys: ${{ runner.os }}-cargo-asan-
64+
65+
- name: Configure and build nginx
66+
working-directory: nginx
67+
env:
68+
CFLAGS: >-
69+
-DNGX_DEBUG_PALLOC=1
70+
-DNGX_SUPPRESS_WARN=1
71+
-O1
72+
-fno-omit-frame-pointer
73+
-fsanitize=address,undefined
74+
LDFLAGS: -fsanitize=address,undefined
75+
RUST_TARGET: x86_64-unknown-linux-gnu
76+
RUSTC_BOOTSTRAP: 1
77+
RUSTFLAGS: -Zsanitizer=address -Zexternal-clangrt
78+
# extra options passed to cargo rustc
79+
NGX_RUSTC_OPT: -Zbuild-std
80+
run: |
81+
patch -p1 < $GITHUB_WORKSPACE/misc/nginx-sanitizer-support.patch
82+
auto/configure \
83+
--with-cc=clang \
84+
--with-cc-opt="$CFLAGS" \
85+
--with-ld-opt="$LDFLAGS" \
86+
--with-compat \
87+
--with-debug \
88+
--with-http_ssl_module \
89+
--with-http_v2_module \
90+
--with-http_v3_module \
91+
--with-stream \
92+
--with-stream_ssl_module \
93+
--with-threads \
94+
--add-module=$(realpath ../examples)
95+
make -j$(nproc)
96+
97+
- name: Run tests
98+
env:
99+
ASAN_OPTIONS: detect_stack_use_after_return=1:detect_odr_violation=0
100+
# `container` job steps are running as root, and thus all the files
101+
# created by the test scripts are owned by root.
102+
# But the worker processes are spawned as "nobody" by default,
103+
# resulting in permission errors.
104+
TEST_NGINX_GLOBALS: >-
105+
user root;
106+
run: |
107+
TEST_NGINX_BINARY="$PWD/nginx/objs/nginx" \
108+
LSAN_OPTIONS="suppressions=$PWD/misc/lsan-suppressions.txt" \
109+
UBSAN_OPTIONS="suppressions=$PWD/misc/ubsan-suppressions.txt" \
110+
prove -v -Inginx/tests/lib examples/t

misc/lsan-suppressions.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# LeakSanitizer suppressions list for nginx
2+
#
3+
# To be used with -fsanitize=address and
4+
# LSAN_OPTIONS=suppressions=lsan-suppressions.txt.
5+
#
6+
# https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#suppressions
7+
8+
# cycle->connections, cycle->read_events, cycle->write_events
9+
leak:ngx_event_process_init
10+
11+
# XXX: can silence leaks from nginx SSL callbacks
12+
leak:SSL_do_handshake
13+
leak:SSL_read
14+
15+
# rcf->ranges not freed at process exit
16+
leak:ngx_http_upstream_update_random
17+
leak:ngx_stream_upstream_update_random

misc/nginx-sanitizer-support.patch

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
diff --git a/src/core/ngx_string.h b/src/core/ngx_string.h
2+
index 713eb42a7..999c6b25f 100644
3+
--- a/src/core/ngx_string.h
4+
+++ b/src/core/ngx_string.h
5+
@@ -96,6 +96,23 @@ void ngx_explicit_memzero(void *buf, size_t n);
6+
void *ngx_memcpy(void *dst, const void *src, size_t n);
7+
#define ngx_cpymem(dst, src, n) (((u_char *) ngx_memcpy(dst, src, n)) + (n))
8+
9+
+#elif (NGX_SUPPRESS_WARN)
10+
+
11+
+/*
12+
+ * Checked versions for sanitizers.
13+
+ * See https://mailman.nginx.org/pipermail/nginx-devel/2023-December/7VNQZEBNXEKAYTYE4Y65FORF4HNELM6V.html
14+
+ */
15+
+
16+
+static ngx_inline void *
17+
+ngx_memcpy(void *dst, const void *src, size_t n) {
18+
+ return (n == 0) ? dst : memcpy(dst, src, n);
19+
+}
20+
+
21+
+static ngx_inline void *
22+
+ngx_cpymem(void *dst, const void *src, size_t n) {
23+
+ return (n == 0) ? dst : ((u_char *) memcpy(dst, src, n)) + n;
24+
+}
25+
+
26+
#else
27+
28+
/*

misc/ubsan-suppressions.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# UndefinedBehaviorSanitizer suppressions list for nginx
2+
#
3+
# To be used with -fsanitize=undefined and
4+
# UBSAN_OPTIONS=suppressions=ubsan-suppressions.txt.
5+
#
6+
# https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#runtime-suppressions
7+
8+
# src/http/v2/ngx_http_v2.c:2747:17: runtime error: store to misaligned address 0x7b35a1a19885 for type 'uint32_t' (aka 'unsigned int'), which requires 4 byte alignment
9+
alignment:src/http/v2/ngx_http_v2.c
10+
alignment:src/http/v2/ngx_http_v2_filter_module.c
11+
alignment:ngx_http_huff_encode
12+
alignment:ngx_http_parse_request_line
13+
14+
# ngx_quic_write_uint32 at src/event/quic/ngx_event_quic_transport.c:642
15+
alignment:ngx_quic_create_long_header
16+
alignment:ngx_quic_read_uint32
17+
18+
# src/core/ngx_output_chain.c:70:20: runtime error: call to function ngx_http_trailers_filter through pointer to incorrect function type 'long (*)(void *, struct ngx_chain_s *)'
19+
# src/http/modules/ngx_http_headers_filter_module.c:249: note: ngx_http_trailers_filter defined here
20+
function:ngx_output_chain
21+
22+
# violates nonnull on memcmp
23+
nonnull-attribute:ngx_http_upstream_zone_preresolve
24+
nonnull-attribute:ngx_stream_upstream_zone_preresolve
25+
26+
# src/http/ngx_http_script.c:800:16: runtime error: applying non-zero offset 112 to null pointer
27+
pointer-overflow:ngx_http_script_add_code
28+
pointer-overflow:ngx_stream_script_add_code

0 commit comments

Comments
 (0)