Skip to content

Commit 93d9574

Browse files
authored
GODRIVER-2753 Integration with oss-fuzz fuzzing service (#1170)
1 parent ccf2d23 commit 93d9574

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

cmd/build-oss-fuzz-corpus/main.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright (C) MongoDB, Inc. 2023-present.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
// not use this file except in compliance with the License. You may obtain
5+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
7+
package main
8+
9+
import (
10+
"archive/zip"
11+
"crypto/sha1"
12+
"encoding/json"
13+
"fmt"
14+
"io/ioutil"
15+
"log"
16+
"os"
17+
"path"
18+
"path/filepath"
19+
20+
"go.mongodb.org/mongo-driver/bson"
21+
)
22+
23+
const dataDir = "testdata/bson-corpus/"
24+
25+
type validityTestCase struct {
26+
Description string `json:"description"`
27+
CanonicalExtJSON string `json:"canonical_extjson"`
28+
RelaxedExtJSON *string `json:"relaxed_extjson"`
29+
DegenerateExtJSON *string `json:"degenerate_extjson"`
30+
ConvertedExtJSON *string `json:"converted_extjson"`
31+
}
32+
33+
func findJSONFilesInDir(dir string) ([]string, error) {
34+
files := make([]string, 0)
35+
36+
entries, err := ioutil.ReadDir(dir)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
for _, entry := range entries {
42+
if entry.IsDir() || path.Ext(entry.Name()) != ".json" {
43+
continue
44+
}
45+
46+
files = append(files, entry.Name())
47+
}
48+
49+
return files, nil
50+
}
51+
52+
// jsonToNative decodes the extended JSON string (ej) into a native Document
53+
func jsonToNative(ej, ejType, testDesc string) (bson.D, error) {
54+
var doc bson.D
55+
if err := bson.UnmarshalExtJSON([]byte(ej), ejType != "relaxed", &doc); err != nil {
56+
return nil, fmt.Errorf("%s: decoding %s extended JSON: %w", testDesc, ejType, err)
57+
}
58+
return doc, nil
59+
}
60+
61+
// jsonToBytes decodes the extended JSON string (ej) into canonical BSON and then encodes it into a byte slice.
62+
func jsonToBytes(ej, ejType, testDesc string) ([]byte, error) {
63+
native, err := jsonToNative(ej, ejType, testDesc)
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
b, err := bson.Marshal(native)
69+
if err != nil {
70+
return nil, fmt.Errorf("%s: encoding %s BSON: %w", testDesc, ejType, err)
71+
}
72+
73+
return b, nil
74+
}
75+
76+
// seedExtJSON will add the byte representation of the "extJSON" string to the fuzzer's coprus.
77+
func seedExtJSON(zw *zip.Writer, extJSON string, extJSONType string, desc string) {
78+
jbytes, err := jsonToBytes(extJSON, extJSONType, desc)
79+
if err != nil {
80+
log.Fatalf("failed to convert JSON to bytes: %v", err)
81+
}
82+
83+
zipFile := fmt.Sprintf("%x", sha1.Sum(jbytes))
84+
85+
f, err := zw.Create(zipFile)
86+
if err != nil {
87+
log.Fatalf("error creating zip file: %v", err)
88+
}
89+
90+
_, err = f.Write(jbytes)
91+
if err != nil {
92+
log.Fatalf("failed to write file: %s into zip file: %v", zipFile, err)
93+
}
94+
}
95+
96+
// seedTestCase will add the byte representation for each "extJSON" string of each valid test case to the fuzzer's
97+
// corpus.
98+
func seedTestCase(zw *zip.Writer, tcase []*validityTestCase) {
99+
for _, vtc := range tcase {
100+
seedExtJSON(zw, vtc.CanonicalExtJSON, "canonical", vtc.Description)
101+
102+
// Seed the relaxed extended JSON.
103+
if vtc.RelaxedExtJSON != nil {
104+
seedExtJSON(zw, *vtc.RelaxedExtJSON, "relaxed", vtc.Description)
105+
}
106+
107+
// Seed the degenerate extended JSON.
108+
if vtc.DegenerateExtJSON != nil {
109+
seedExtJSON(zw, *vtc.DegenerateExtJSON, "degenerate", vtc.Description)
110+
}
111+
112+
// Seed the converted extended JSON.
113+
if vtc.ConvertedExtJSON != nil {
114+
seedExtJSON(zw, *vtc.ConvertedExtJSON, "converted", vtc.Description)
115+
}
116+
}
117+
}
118+
119+
// seedBSONCorpus will unmarshal the data from "testdata/bson-corpus" into a slice of "testCase" structs and then
120+
// marshal the "*_extjson" field of each "validityTestCase" into a slice of bytes to seed the fuzz corpus.
121+
func seedBSONCorpus(zw *zip.Writer) {
122+
fileNames, err := findJSONFilesInDir(dataDir)
123+
if err != nil {
124+
log.Fatalf("failed to find JSON files in directory %q: %v", dataDir, err)
125+
}
126+
127+
for _, fileName := range fileNames {
128+
filePath := path.Join(dataDir, fileName)
129+
130+
file, err := os.Open(filePath)
131+
if err != nil {
132+
log.Fatalf("failed to open file %q: %v", filePath, err)
133+
}
134+
135+
tc := struct {
136+
Valid []*validityTestCase `json:"valid"`
137+
}{}
138+
139+
if err := json.NewDecoder(file).Decode(&tc); err != nil {
140+
log.Fatalf("failed to decode file %q: %v", filePath, err)
141+
}
142+
143+
seedTestCase(zw, tc.Valid)
144+
}
145+
}
146+
147+
// main packs the local corpus as <fuzzer_name>_seed_corpus.zip, which is used by OSS-Fuzz to seed remote fuzzing
148+
// of the MongoDB Go Driver. See here for more details: https://google.github.io/oss-fuzz/architecture/
149+
func main() {
150+
seedCorpus := os.Args[1]
151+
if filepath.Ext(seedCorpus) != ".zip" {
152+
log.Fatalf("expected zip file <corpus>.zip, got %s", seedCorpus)
153+
}
154+
155+
zipFile, err := os.Create(seedCorpus)
156+
if err != nil {
157+
log.Fatalf("failed creating zip file: %v", err)
158+
}
159+
160+
defer func() {
161+
err := zipFile.Close()
162+
if err != nil {
163+
log.Fatalf("failed to close zip file: %v", err)
164+
}
165+
}()
166+
167+
zipWriter := zip.NewWriter(zipFile)
168+
seedBSONCorpus(zipWriter)
169+
170+
if err := zipWriter.Close(); err != nil {
171+
log.Fatalf("failed to close zip writer: %v", err)
172+
}
173+
}

0 commit comments

Comments
 (0)