Skip to content

Commit 397baa5

Browse files
authored
nodejs: migrate fuzzers to oss-fuzz (#14143)
Since last year, I have maintained many of Node.js's fuzzers from a fork of mine to ensure they ran over a longer period of time. This PR migrates the fuzzers I've had on my fork over to OSS-Fuzz. Their final destination should be upstream, but we may still need to iterate over these a few times which is why OSS-Fuzz is a good place for now. The PR includes the fuzzers, fuzz helpers and a script that adds the fuzzers to node.js's `node.gyp` file so they can be built in node.js's build system. The PR also removes the sha from the base-builder in the Dockerfile since it breaks the build. --------- Signed-off-by: Adam Korczynski <[email protected]>
1 parent 36cd876 commit 397baa5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2931
-91
lines changed

projects/nodejs/Dockerfile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@
1414
#
1515
################################################################################
1616

17-
FROM gcr.io/oss-fuzz-base/base-builder@sha256:d34b94e3cf868e49d2928c76ddba41fd4154907a1a381b3a263fafffb7c3dce0
17+
FROM gcr.io/oss-fuzz-base/base-builder
1818
RUN apt-get update && apt-get install -y make flex bison build-essential
19-
RUN git clone --recursive --depth 1 https://github.com/AdamKorcz/node --branch=all-new-fuzzers
20-
WORKDIR $SRC
21-
COPY build.sh $SRC/
19+
RUN git clone --recursive --depth 1 https://github.com/nodejs/node
20+
RUN wget https://go.dev/dl/go1.24.5.linux-amd64.tar.gz \
21+
&& mkdir temp-go \
22+
&& mkdir mkdir /root/.go/ \
23+
&& rm -rf /root/.go/* \
24+
&& tar -C temp-go/ -xzf go1.24.5.linux-amd64.tar.gz \
25+
&& mv temp-go/go/* /root/.go/
26+
ENV PATH=$PATH:/root/.go/bin:$GOPATH/bin
27+
WORKDIR $SRC/node
28+
ADD fuzz_sources $SRC/fuzz_sources
29+
COPY *.sh *.cc *.h *add_fuzzers_to_node_gyp.go $SRC/
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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+
package main
17+
18+
import (
19+
"bufio"
20+
"fmt"
21+
"log"
22+
"os"
23+
"strings"
24+
)
25+
26+
var (
27+
template1 = `
28+
{
29+
'target_name': '____TARGETNAME____',
30+
'type': 'executable',
31+
'dependencies': [
32+
'<(node_lib_target_name)',
33+
'deps/googletest/googletest.gyp:gtest_prod',
34+
'deps/histogram/histogram.gyp:histogram',
35+
'deps/uvwasi/uvwasi.gyp:uvwasi',
36+
'deps/ncrypto/ncrypto.gyp:ncrypto',
37+
'deps/nbytes/nbytes.gyp:nbytes',
38+
'tools/v8_gypfiles/abseil.gyp:abseil',
39+
],
40+
'includes': [
41+
'node.gypi'
42+
],
43+
'include_dirs': [
44+
'src',
45+
'tools/msvs/genfiles',
46+
'deps/v8/include',
47+
'deps/cares/include',
48+
'deps/uv/include',
49+
'deps/uvwasi/include',
50+
'test/cctest',
51+
'test/fuzzers',
52+
],
53+
'defines': [
54+
'NODE_ARCH="<(target_arch)"',
55+
'NODE_PLATFORM="<(OS)"',
56+
'NODE_WANT_INTERNALS=1',
57+
'HAVE_OPENSSL=1',
58+
'NAPI_VERSION=10',
59+
],
60+
'sources': [
61+
'src/node_snapshot_stub.cc',
62+
'test/fuzzers/fuzz_common.cc',
63+
'test/fuzzers/____TARGETNAME____.cc',
64+
],
65+
'conditions': [
66+
['OS=="linux"', {
67+
'ldflags': [ '-fsanitize=fuzzer' ]
68+
}],
69+
# Ensure that ossfuzz flag has been set and that we are on Linux
70+
[ 'OS!="linux" or ossfuzz!="true"', {
71+
'type': 'none',
72+
}],
73+
# Avoid excessive LTO
74+
['enable_lto=="true"', {
75+
'ldflags': [ '-fno-lto' ],
76+
}],
77+
],
78+
},`
79+
fuzzers = []string{
80+
"fuzz_ClientHelloParser",
81+
"fuzz_blob",
82+
"fuzz_buffer_compare",
83+
"fuzz_buffer_equals",
84+
"fuzz_buffer_includes",
85+
"fuzz_cipheriv",
86+
"fuzz_createPrivateKeyDER",
87+
"fuzz_createPrivateKeyJWK",
88+
"fuzz_createPrivateKeyPEM",
89+
"fuzz_diffieHellmanDER",
90+
"fuzz_diffieHellmanJWK",
91+
"fuzz_diffieHellmanPEM",
92+
"fuzz_fs_write_open_read",
93+
"fuzz_fs_write_read_append",
94+
"fuzz_httpparser1",
95+
"fuzz_path_basename",
96+
"fuzz_path_dirname",
97+
"fuzz_path_extname",
98+
"fuzz_path_format",
99+
"fuzz_path_isAbsolute",
100+
"fuzz_path_join",
101+
"fuzz_path_normalize",
102+
"fuzz_path_parse",
103+
"fuzz_path_relative",
104+
"fuzz_path_resolve",
105+
"fuzz_path_toNamespacedPath",
106+
"fuzz_querystring_parse",
107+
"fuzz_quic_token",
108+
"fuzz_sign_verify",
109+
"fuzz_stream1",
110+
"fuzz_string_decoder",
111+
"fuzz_strings",
112+
"fuzz_tls_socket_request",
113+
"fuzz_v8_deserialize",
114+
"fuzz_x509",
115+
"fuzz_zlib_brotliCompress",
116+
"fuzz_zlib_brotliDecompress",
117+
"fuzz_zlib_createBrotliDecompress",
118+
"fuzz_zlib_gzip_createUnzip",
119+
}
120+
)
121+
122+
func createGypTargetEntry(fuzzerName string) string {
123+
templ := template1
124+
templWithTargetName := strings.ReplaceAll(templ, "____TARGETNAME____", fuzzerName)
125+
return templWithTargetName
126+
}
127+
128+
func main() {
129+
if len(os.Args) < 3 {
130+
fmt.Println("Usage: go run main.go <inputfile> <outputfile>")
131+
return
132+
}
133+
134+
inputFile := os.Args[1]
135+
outputFile := os.Args[2]
136+
137+
in, err := os.Open(inputFile)
138+
if err != nil {
139+
log.Fatalf("Failed to open input file: %v", err)
140+
}
141+
defer in.Close()
142+
143+
out, err := os.Create(outputFile)
144+
if err != nil {
145+
log.Fatalf("Failed to create output file: %v", err)
146+
}
147+
defer out.Close()
148+
149+
scanner := bufio.NewScanner(in)
150+
writer := bufio.NewWriter(out)
151+
152+
var ignore bool
153+
154+
for scanner.Scan() {
155+
line := scanner.Text()
156+
157+
// Remove existing fuzzers in the original node.gyp
158+
if !ignore && line == " { # fuzz_env" {
159+
ignore = true
160+
continue
161+
} else if ignore && line == " }, # fuzz_env" {
162+
ignore = false
163+
continue
164+
} else if !ignore && line == " { # fuzz_ClientHelloParser.cc" {
165+
ignore = true
166+
continue
167+
} else if ignore && line == " }, # fuzz_ClientHelloParser.cc" {
168+
ignore = false
169+
continue
170+
} else if !ignore && line == " { # fuzz_url" {
171+
ignore = true
172+
continue
173+
} else if ignore && line == " }, # fuzz_url" {
174+
ignore = false
175+
continue
176+
} else if !ignore && line == " { # fuzz_strings" {
177+
ignore = true
178+
continue
179+
} else if ignore && line == " }, # fuzz_strings" {
180+
ignore = false
181+
continue
182+
}
183+
if ignore {
184+
continue
185+
}
186+
187+
var stringToAppend string
188+
appendTargetsNow := line == " 'targets': ["
189+
// add new line unless we are adding the fuzzers
190+
// since the fuzzer template already has new line
191+
if appendTargetsNow {
192+
stringToAppend = line
193+
} else {
194+
stringToAppend = line + "\n"
195+
}
196+
_, err := writer.WriteString(stringToAppend)
197+
if err != nil {
198+
log.Fatalf("Failed to write line: %v", err)
199+
}
200+
201+
// Check for the special line
202+
if appendTargetsNow {
203+
for _, fuzzer := range fuzzers {
204+
fmt.Println("appending ", fuzzer)
205+
_, err := writer.WriteString(createGypTargetEntry(fuzzer))
206+
if err != nil {
207+
log.Fatalf("Failed to write injected line: %v", err)
208+
}
209+
}
210+
}
211+
}
212+
213+
if err := scanner.Err(); err != nil {
214+
log.Fatalf("Error reading input file: %v", err)
215+
}
216+
217+
if err := writer.Flush(); err != nil {
218+
log.Fatalf("Error flushing output: %v", err)
219+
}
220+
}

projects/nodejs/build.sh

Lines changed: 60 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@
1414
# limitations under the License.
1515
#
1616
################################################################################
17+
rm $SRC/node/test/fuzzers/*
18+
cp $SRC/fuzz_sources/* $SRC/node/test/fuzzers/
19+
20+
# Add the fuzzers to node.gyp
21+
mkdir $SRC/modify-node-gyp
22+
cd $SRC/modify-node-gyp
23+
go mod init modify-node-gyp
24+
mv $SRC/add_fuzzers_to_node_gyp.go ./main.go
25+
go run main.go $SRC/node/node.gyp /tmp/updated-node.gyp
26+
mv /tmp/updated-node.gyp $SRC/node/node.gyp
27+
1728
cd $SRC/node
1829

1930
# Coverage build takes very long and time outs in the CI which blocks changes. Ignore Coverage build in OSS-Fuzz CI for now:
@@ -31,75 +42,62 @@ if [[ "$SANITIZER" = coverage ]]; then
3142
fi
3243

3344
# Build node
45+
export CXXFLAGS="$CXXFLAGS -std=c++20 -stdlib=libc++"
46+
export GN_ARGS='use_custom_libcxx=true'
3447
export LDFLAGS="$CXXFLAGS"
48+
export LDFLAGS="$LDFLAGS -stdlib=libc++"
3549
export LD="$CXX"
3650
./configure --with-ossfuzz
3751

3852
# Ensure we build with few processors if memory gets exhausted
3953
if [[ "$SANITIZER" = coverage ]]; then
40-
for mrkpath in \
41-
fuzz_buffer_includes.target.mk \
42-
fuzz_buffer_equals.target.mk \
43-
fuzz_buffer_compare.target.mk \
44-
fuzz_blob.target.mk \
45-
fuzz_zlib_gzip_createUnzip.target.mk \
46-
fuzz_zlib_createBrotliDecompress.target.mk \
47-
fuzz_zlib_brotliDecompress.target.mk \
48-
fuzz_zlib_brotliCompress.target.mk \
49-
fuzz_string_decoder.target.mk \
50-
fuzz_querystring_parse.target.mk \
51-
fuzz_path_join.target.mk \
52-
fuzz_stream1.target.mk \
53-
fuzz_strings.target.mk \
54-
fuzz_diffieHellmanPEM.target.mk \
55-
fuzz_createPrivateKeyPEM.target.mk \
56-
fuzz_createPrivateKeyDER.target.mk \
57-
fuzz_path_extname.target.mk \
58-
fuzz_path_normalize.target.mk \
59-
fuzz_path_relative.target.mk \
60-
fuzz_createPrivateKeyJWK.target.mk \
61-
fuzz_path_format.target.mk \
62-
fuzz_ClientHelloParser.target.mk \
63-
fuzz_diffieHellmanJWK.target.mk \
64-
fuzz_path_basename.target.mk \
65-
fuzz_path_isAbsolute.target.mk \
66-
fuzz_tls_socket_request.target.mk \
67-
fuzz_diffieHellmanDER.target.mk \
68-
fuzz_path_toNamespacedPath.target.mk \
69-
fuzz_path_parse.target.mk \
70-
fuzz_httpparser1.target.mk \
71-
fuzz_path_dirname.target.mk \
72-
fuzz_x509.target.mk \
73-
fuzz_fs_write_read_append.target.mk \
74-
fuzz_sign_verify.target.mk \
75-
fuzz_path_resolve.target.mk \
76-
fuzz_fs_write_open_read.target.mk \
77-
libnode.target.mk
78-
do
79-
echo "sed'ing ${mrkpath}"
80-
sed -i 's/BUILDTYPE))/BUILDTYPE)) -fprofile-instr-generate -fcoverage-mapping/g' "$SRC/node/out/${mrkpath}"
81-
done
82-
make -j 3 || make -j1
54+
for mrkpath in \
55+
fuzz_buffer_includes.target.mk \
56+
fuzz_buffer_equals.target.mk \
57+
fuzz_buffer_compare.target.mk \
58+
fuzz_blob.target.mk \
59+
fuzz_zlib_gzip_createUnzip.target.mk \
60+
fuzz_zlib_createBrotliDecompress.target.mk \
61+
fuzz_zlib_brotliDecompress.target.mk \
62+
fuzz_zlib_brotliCompress.target.mk \
63+
fuzz_string_decoder.target.mk \
64+
fuzz_querystring_parse.target.mk \
65+
fuzz_path_join.target.mk \
66+
fuzz_stream1.target.mk \
67+
fuzz_strings.target.mk \
68+
fuzz_diffieHellmanPEM.target.mk \
69+
fuzz_createPrivateKeyPEM.target.mk \
70+
fuzz_createPrivateKeyDER.target.mk \
71+
fuzz_path_extname.target.mk \
72+
fuzz_path_normalize.target.mk \
73+
fuzz_path_relative.target.mk \
74+
fuzz_createPrivateKeyJWK.target.mk \
75+
fuzz_path_format.target.mk \
76+
fuzz_ClientHelloParser.target.mk \
77+
fuzz_diffieHellmanJWK.target.mk \
78+
fuzz_path_basename.target.mk \
79+
fuzz_path_isAbsolute.target.mk \
80+
fuzz_tls_socket_request.target.mk \
81+
fuzz_diffieHellmanDER.target.mk \
82+
fuzz_path_toNamespacedPath.target.mk \
83+
fuzz_path_parse.target.mk \
84+
fuzz_httpparser1.target.mk \
85+
fuzz_path_dirname.target.mk \
86+
fuzz_x509.target.mk \
87+
fuzz_fs_write_read_append.target.mk \
88+
fuzz_sign_verify.target.mk \
89+
fuzz_path_resolve.target.mk \
90+
fuzz_fs_write_open_read.target.mk \
91+
libnode.target.mk
92+
do
93+
echo "sed'ing ${mrkpath}"
94+
sed -i 's/BUILDTYPE))/BUILDTYPE)) -fprofile-instr-generate -fcoverage-mapping/g' "$SRC/node/out/${mrkpath}"
95+
done
96+
make -j3 || make -j1
8397
else
84-
make -j$(nproc) || make -j1
98+
make -j$(nproc) || make -j1
8599
fi
86100

87-
# Copy all fuzzers to OUT folder
88-
cp out/Release/fuzz_* ${OUT}/
89-
90-
# Create seed for fuzz_env
91-
mkdir fuzz_env_seed
92-
find ./test -name '*.js' -exec cp {} ./fuzz_env_seed/ \;
93-
cd fuzz_env_seed
94-
# Remove small files:
95-
find -size -5k -delete
96-
# Remove large files:
97-
find -size +30k -delete
98-
zip $OUT/fuzz_env_seed_corpus.zip ./*
99-
# Add more seeds
100-
cd $SRC/node/test/fuzzers/seed/fuzz_env
101-
zip $OUT/fuzz_env_seed_corpus.zip ./*
102-
103-
cd $SRC/node/test/fuzzers/seed/fuzz_x509
104-
zip $OUT/fuzz_x509_seed_corpus.zip ./*
101+
# Move all fuzzers to OUT folder
102+
mv out/Release/fuzz_* ${OUT}/
105103

0 commit comments

Comments
 (0)