Skip to content

Commit 74588fd

Browse files
committed
Add rsync fuzzing integration
Add fuzz targets for rsync: - fuzz_wildmatch: tests wildcard pattern matching (wildmatch/iwildmatch) - fuzz_parse_filter: tests filter rule parsing (parse_filter_str/check_filter) - fuzz_rsyncd_conf: tests rsyncd.conf config parsing (lp_load) Includes dictionaries for all three targets and an options file to suppress expected memory leaks in parse_filter_str.
1 parent fc9bd85 commit 74588fd

10 files changed

Lines changed: 558 additions & 0 deletions

projects/rsync/Dockerfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
################################################################################
16+
17+
FROM gcr.io/oss-fuzz-base/base-builder
18+
RUN apt-get update && apt-get install -y \
19+
make autoconf automake libtool pkg-config \
20+
libz-dev libssl-dev liblz4-dev libzstd-dev libxxhash-dev \
21+
libpopt-dev
22+
RUN git clone --depth 1 https://github.com/RsyncProject/rsync rsync
23+
WORKDIR rsync
24+
COPY build.sh fuzz_wildmatch.c fuzz_parse_filter.c fuzz_rsyncd_conf.c \
25+
fuzz_parse_filter.options \
26+
wildmatch.dict filter_rules.dict rsyncd_conf.dict $SRC/

projects/rsync/build.sh

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/bin/bash -eu
2+
# Copyright 2025 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
################################################################################
17+
18+
cd $SRC/rsync
19+
20+
# Build rsync with fuzzing-friendly flags
21+
# Use the shipped configure.sh if available, otherwise generate it
22+
if [ -f configure.sh ]; then
23+
./configure.sh \
24+
--disable-md2man \
25+
--disable-xxhash \
26+
--disable-zstd \
27+
--disable-lz4 \
28+
--with-included-popt \
29+
CC="$CC" \
30+
CFLAGS="$CFLAGS" \
31+
LDFLAGS="$CFLAGS"
32+
else
33+
autoreconf -i
34+
./configure \
35+
--disable-md2man \
36+
--disable-xxhash \
37+
--disable-zstd \
38+
--disable-lz4 \
39+
--with-included-popt \
40+
CC="$CC" \
41+
CFLAGS="$CFLAGS" \
42+
LDFLAGS="$CFLAGS"
43+
fi
44+
45+
# Prevent make from trying to regenerate autoconf files
46+
touch configure.sh config.h.in config.h Makefile
47+
48+
make -j$(nproc) || true
49+
50+
# Create a static library from all rsync object files (excluding main.o, cleanup.o, and test tools)
51+
RSYNC_OBJS=$(find . -name '*.o' ! -name 'main.o' ! -name 'cleanup.o' ! -name 'wildtest.o' \
52+
! -name 'trimslash.o' ! -name 't_unsafe.o' ! -name 'testrun.o' \
53+
! -name 'tls.o' ! -name 'getgroups.o' ! -name 'getfsdev.o' \
54+
! -name 't_stub.o' ! -name 'fuzz_*.o' | tr '\n' ' ')
55+
56+
if [ -z "$RSYNC_OBJS" ]; then
57+
echo "ERROR: No .o files found. Make likely failed."
58+
exit 1
59+
fi
60+
61+
ar rcs librsync_fuzz.a $RSYNC_OBJS
62+
63+
# Find static libcrypto for linking
64+
LIBCRYPTO=$(find /usr/lib* -name 'libcrypto.a' -print -quit 2>/dev/null || echo "")
65+
if [ -z "$LIBCRYPTO" ]; then
66+
LIBCRYPTO="-lcrypto"
67+
fi
68+
69+
# Build fuzz_wildmatch — links against just the needed objects
70+
$CC $CFLAGS -I. -I$SRC/rsync -c $SRC/fuzz_wildmatch.c -o fuzz_wildmatch.o
71+
$CXX $CXXFLAGS -I. -o $OUT/fuzz_wildmatch \
72+
fuzz_wildmatch.o librsync_fuzz.a \
73+
$LIB_FUZZING_ENGINE $LIBCRYPTO
74+
75+
# Build fuzz_parse_filter
76+
$CC $CFLAGS -I. -I$SRC/rsync -c $SRC/fuzz_parse_filter.c -o fuzz_parse_filter.o
77+
$CXX $CXXFLAGS -I. -o $OUT/fuzz_parse_filter \
78+
fuzz_parse_filter.o librsync_fuzz.a \
79+
$LIB_FUZZING_ENGINE $LIBCRYPTO
80+
81+
# Build fuzz_rsyncd_conf
82+
$CC $CFLAGS -I. -I$SRC/rsync -c $SRC/fuzz_rsyncd_conf.c -o fuzz_rsyncd_conf.o
83+
$CXX $CXXFLAGS -I. -o $OUT/fuzz_rsyncd_conf \
84+
fuzz_rsyncd_conf.o librsync_fuzz.a \
85+
$LIB_FUZZING_ENGINE $LIBCRYPTO
86+
87+
# Copy dictionaries and options files
88+
cp $SRC/wildmatch.dict $OUT/fuzz_wildmatch.dict
89+
cp $SRC/filter_rules.dict $OUT/fuzz_parse_filter.dict
90+
cp $SRC/rsyncd_conf.dict $OUT/fuzz_rsyncd_conf.dict
91+
cp $SRC/fuzz_parse_filter.options $OUT/fuzz_parse_filter.options

projects/rsync/filter_rules.dict

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Filter rule dictionary for rsync's parse_filter_str()
2+
"- "
3+
"+ "
4+
"- *.o"
5+
"+ *.c"
6+
"- /foo"
7+
"+ /bar/"
8+
". "
9+
": "
10+
"! "
11+
"H "
12+
"S "
13+
"R "
14+
"P "
15+
"exclude "
16+
"include "
17+
"merge "
18+
"dir-merge "
19+
"hide "
20+
"show "
21+
"protect "
22+
"risk "
23+
"clear "
24+
",s "
25+
",r "
26+
",p "
27+
",n "
28+
",w "
29+
",e "
30+
",x "
31+
",- "
32+
",+ "
33+
"- *.o"
34+
"+ *.c"
35+
"- /tmp/"
36+
"+ /src/**"
37+
"- ***"
38+
"- */"
39+
":C"
40+
"-C"
41+
":n- .gitignore"

projects/rsync/fuzz_parse_filter.c

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* Copyright 2025 Google LLC
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
#include "rsync.h"
17+
#include <setjmp.h>
18+
19+
static jmp_buf fuzz_exit_jmp;
20+
21+
/* These are defined in main.c which we don't link against */
22+
int am_receiver = 0;
23+
int am_generator = 0;
24+
int local_server = 0;
25+
mode_t orig_umask = 022;
26+
int batch_gen_fd = -1;
27+
int sender_keeps_checksum = 0;
28+
char *raw_argv[1] = {NULL};
29+
int raw_argc = 0;
30+
int cooked_argc = 0;
31+
char **cooked_argv = NULL;
32+
uid_t our_uid = 0;
33+
gid_t our_gid = 0;
34+
int daemon_connection = 0;
35+
36+
/* Stubs for functions defined in main.c */
37+
void remember_children(UNUSED(int val)) {}
38+
void start_server(UNUSED(int f_in), UNUSED(int f_out),
39+
UNUSED(int argc), UNUSED(char *argv[])) {}
40+
int client_run(UNUSED(int f_in), UNUSED(int f_out),
41+
UNUSED(pid_t pid), UNUSED(int argc), UNUSED(char *argv[])) { return 0; }
42+
pid_t wait_process(UNUSED(pid_t pid), UNUSED(int *status_ptr),
43+
UNUSED(int flags)) { return -1; }
44+
int shell_exec(UNUSED(const char *cmd)) { return -1; }
45+
void read_del_stats(UNUSED(int f)) {}
46+
void write_del_stats(UNUSED(int f)) {}
47+
48+
/* Stubs for cleanup.c which we exclude to avoid exit() calls */
49+
pid_t cleanup_child_pid = -1;
50+
int cleanup_got_literal = 0;
51+
int called_from_signal_handler = 0;
52+
BOOL flush_ok_after_signal = False;
53+
NORETURN void _exit_cleanup(UNUSED(int code), UNUSED(const char *file), UNUSED(int line)) {
54+
longjmp(fuzz_exit_jmp, 1);
55+
}
56+
void cleanup_disable(void) {}
57+
void cleanup_set(UNUSED(const char *fnametmp), UNUSED(const char *fname),
58+
UNUSED(struct file_struct *file), UNUSED(int fd_r), UNUSED(int fd_w)) {}
59+
void cleanup_set_pid(UNUSED(pid_t pid)) {}
60+
void close_all(void) {}
61+
62+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
63+
if (size < 1 || size > 8192)
64+
return 0;
65+
66+
/* Null-terminate the input */
67+
char *input = (char *)malloc(size + 1);
68+
if (!input)
69+
return 0;
70+
71+
memcpy(input, data, size);
72+
input[size] = '\0';
73+
74+
/* Use setjmp to catch rsync's exit_cleanup() calls */
75+
if (setjmp(fuzz_exit_jmp) != 0) {
76+
free(input);
77+
return 0;
78+
}
79+
80+
/* Initialize a fresh filter list */
81+
filter_rule_list flist = { .head = NULL, .tail = NULL, .debug_type = "" };
82+
83+
/* Parse the input as a filter rule string */
84+
parse_filter_str(&flist, input, rule_template(0), 0);
85+
86+
/* Also try with OLD_PREFIXES flag for compatibility parsing */
87+
filter_rule_list flist2 = { .head = NULL, .tail = NULL, .debug_type = "" };
88+
parse_filter_str(&flist2, input, rule_template(FILTRULE_INCLUDE), XFLG_OLD_PREFIXES);
89+
90+
/* Try with WORD_SPLIT (like daemon config parsing) */
91+
filter_rule_list flist3 = { .head = NULL, .tail = NULL, .debug_type = "" };
92+
parse_filter_str(&flist3, input,
93+
rule_template(FILTRULE_WORD_SPLIT), 0);
94+
95+
/* Exercise check_filter if we got any rules */
96+
if (flist.head) {
97+
check_filter(&flist, FINFO, "test/path/file.c", 0);
98+
check_filter(&flist, FINFO, "test/path/dir", NAME_IS_DIR);
99+
check_filter(&flist, FINFO, ".hidden", 0);
100+
}
101+
102+
free(input);
103+
104+
return 0;
105+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[libfuzzer]
2+
detect_leaks=0

projects/rsync/fuzz_rsyncd_conf.c

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/* Copyright 2025 Google LLC
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
#include "rsync.h"
17+
#include <setjmp.h>
18+
19+
static jmp_buf fuzz_exit_jmp;
20+
21+
/* These are defined in main.c which we don't link against */
22+
int am_receiver = 0;
23+
int am_generator = 0;
24+
int local_server = 0;
25+
mode_t orig_umask = 022;
26+
int batch_gen_fd = -1;
27+
int sender_keeps_checksum = 0;
28+
char *raw_argv[1] = {NULL};
29+
int raw_argc = 0;
30+
int cooked_argc = 0;
31+
char **cooked_argv = NULL;
32+
uid_t our_uid = 0;
33+
gid_t our_gid = 0;
34+
int daemon_connection = 0;
35+
36+
/* Stubs for functions defined in main.c */
37+
void remember_children(UNUSED(int val)) {}
38+
void start_server(UNUSED(int f_in), UNUSED(int f_out),
39+
UNUSED(int argc), UNUSED(char *argv[])) {}
40+
int client_run(UNUSED(int f_in), UNUSED(int f_out),
41+
UNUSED(pid_t pid), UNUSED(int argc), UNUSED(char *argv[])) { return 0; }
42+
pid_t wait_process(UNUSED(pid_t pid), UNUSED(int *status_ptr),
43+
UNUSED(int flags)) { return -1; }
44+
int shell_exec(UNUSED(const char *cmd)) { return -1; }
45+
void read_del_stats(UNUSED(int f)) {}
46+
void write_del_stats(UNUSED(int f)) {}
47+
48+
/* Stubs for cleanup.c which we exclude to avoid exit() calls */
49+
pid_t cleanup_child_pid = -1;
50+
int cleanup_got_literal = 0;
51+
int called_from_signal_handler = 0;
52+
BOOL flush_ok_after_signal = False;
53+
NORETURN void _exit_cleanup(UNUSED(int code), UNUSED(const char *file), UNUSED(int line)) {
54+
longjmp(fuzz_exit_jmp, 1);
55+
}
56+
void cleanup_disable(void) {}
57+
void cleanup_set(UNUSED(const char *fnametmp), UNUSED(const char *fname),
58+
UNUSED(struct file_struct *file), UNUSED(int fd_r), UNUSED(int fd_w)) {}
59+
void cleanup_set_pid(UNUSED(pid_t pid)) {}
60+
void close_all(void) {}
61+
62+
/* lp_load() from loadparm.c loads rsyncd.conf from a file path. */
63+
int lp_load(char *pszFname, int globals_only);
64+
65+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
66+
if (size < 1 || size > 16384)
67+
return 0;
68+
69+
/* Write fuzz data to a temporary file */
70+
char tmpfile[] = "/tmp/fuzz_rsyncd_XXXXXX";
71+
int fd = mkstemp(tmpfile);
72+
if (fd < 0)
73+
return 0;
74+
75+
if (write(fd, data, size) != (ssize_t)size) {
76+
close(fd);
77+
unlink(tmpfile);
78+
return 0;
79+
}
80+
close(fd);
81+
82+
/* Use setjmp to catch rsync's exit_cleanup() calls */
83+
if (setjmp(fuzz_exit_jmp) != 0) {
84+
unlink(tmpfile);
85+
return 0;
86+
}
87+
88+
/* Parse the config file */
89+
lp_load(tmpfile, 0);
90+
91+
/* Also test globals-only mode */
92+
lp_load(tmpfile, 1);
93+
94+
unlink(tmpfile);
95+
96+
return 0;
97+
}

0 commit comments

Comments
 (0)