Skip to content

Commit 6294235

Browse files
committed
Fuzzing: Add container fuzzer
Signed-off-by: AdamKorcz <[email protected]>
1 parent a963242 commit 6294235

2 files changed

Lines changed: 358 additions & 2 deletions

File tree

contrib/fuzz/container_fuzzer.go

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
// +build gofuzz
2+
3+
/*
4+
Copyright The containerd Authors.
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
http://www.apache.org/licenses/LICENSE-2.0
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+
To run this fuzzer, it must first be moved to
18+
integration/client. OSS-fuzz does this automatically
19+
everytime it builds the fuzzers.
20+
*/
21+
22+
package client
23+
24+
import (
25+
"bytes"
26+
"context"
27+
"errors"
28+
"fmt"
29+
"io"
30+
"io/ioutil"
31+
"os"
32+
"os/exec"
33+
"strings"
34+
"time"
35+
36+
fuzz "github.com/AdaLogics/go-fuzz-headers"
37+
38+
"github.com/containerd/containerd"
39+
"github.com/containerd/containerd/oci"
40+
"github.com/containerd/containerd/sys"
41+
)
42+
43+
func init() {
44+
err := updatePathEnv()
45+
if err != nil {
46+
panic(err)
47+
}
48+
}
49+
50+
func tearDown() error {
51+
if err := ctrd.Stop(); err != nil {
52+
if err := ctrd.Kill(); err != nil {
53+
return err
54+
}
55+
}
56+
if err := ctrd.Wait(); err != nil {
57+
if _, ok := err.(*exec.ExitError); !ok {
58+
return err
59+
}
60+
}
61+
if err := sys.ForceRemoveAll(defaultRoot); err != nil {
62+
return err
63+
}
64+
return nil
65+
}
66+
67+
// startDaemon() starts the daemon.
68+
func startDaemon(ctx context.Context) {
69+
buf := bytes.NewBuffer(nil)
70+
stdioFile, err := ioutil.TempFile("", "")
71+
if err != nil {
72+
// We panic here as it is a fuzz-blocker that
73+
// may need fixing
74+
panic(err)
75+
}
76+
defer func() {
77+
stdioFile.Close()
78+
os.Remove(stdioFile.Name())
79+
}()
80+
ctrdStdioFilePath = stdioFile.Name()
81+
stdioWriter := io.MultiWriter(stdioFile, buf)
82+
err = ctrd.start("containerd", address, []string{
83+
"--root", defaultRoot,
84+
"--state", defaultState,
85+
"--log-level", "debug",
86+
"--config", createShimDebugConfig(),
87+
}, stdioWriter, stdioWriter)
88+
if err != nil {
89+
// We are fine if the error is that the daemon is already running,
90+
// but if the error is another, then it will be a fuzz blocker,
91+
// so we panic
92+
if !strings.Contains(err.Error(), "daemon is already running") {
93+
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
94+
panic(err)
95+
}
96+
}
97+
// Whether or not we should tear down after each fuzz iteration
98+
// requires further investigation. The fuzzer runs fine without
99+
// but if there is no performance-penalty, then we should tear down.
100+
// To-do @AdamKorcz
101+
/*defer func() {
102+
err = tearDown()
103+
if err != nil {
104+
panic(err)
105+
}
106+
}()*/
107+
seconds := 4 * time.Second
108+
waitCtx, waitCancel := context.WithTimeout(ctx, seconds)
109+
110+
_, err = ctrd.waitForStart(waitCtx)
111+
waitCancel()
112+
if err != nil {
113+
ctrd.Stop()
114+
ctrd.Kill()
115+
ctrd.Wait()
116+
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
117+
return
118+
}
119+
}
120+
121+
// deleteSocket() deletes the socket in the file system.
122+
// This is needed because the socket occasionally will
123+
// refuse a connection to it, and deleting it allows us
124+
// to create a new socket when invoking containerd.New()
125+
func deleteSocket() error {
126+
cmd := exec.Command("rm", "/run/containerd-test/containerd.sock")
127+
err := cmd.Run()
128+
if err != nil {
129+
return err
130+
}
131+
return nil
132+
}
133+
134+
// updatePathEnv() creates an empty directory in which
135+
// the fuzzer will create the containerd socket.
136+
// updatePathEnv() furthermore adds "/out/containerd-binaries"
137+
// to $PATH, since the binaries are available there.
138+
func updatePathEnv() error {
139+
// Create test dir for socket
140+
err := os.MkdirAll("/run/containerd-test", 0777)
141+
if err != nil {
142+
return err
143+
}
144+
145+
oldPathEnv := os.Getenv("PATH")
146+
newPathEnv := oldPathEnv + ":/out/containerd-binaries"
147+
err = os.Setenv("PATH", newPathEnv)
148+
if err != nil {
149+
return err
150+
}
151+
return nil
152+
}
153+
154+
// checkAndDoUnpack checks if an image is unpacked.
155+
// If it is not unpacked, then we may or may not
156+
// unpack it. The fuzzer decides.
157+
func checkAndDoUnpack(image containerd.Image, ctx context.Context, f *fuzz.ConsumeFuzzer) {
158+
unpacked, err := image.IsUnpacked(ctx, testSnapshotter)
159+
if err == nil && unpacked {
160+
shouldUnpack, err := f.GetBool()
161+
if err == nil && shouldUnpack {
162+
_ = image.Unpack(ctx, testSnapshotter)
163+
}
164+
}
165+
}
166+
167+
// getImage() returns an image from the client.
168+
// The fuzzer decides which image is returned.
169+
func getImage(client *containerd.Client, f *fuzz.ConsumeFuzzer) (containerd.Image, error) {
170+
images, err := client.ListImages(nil)
171+
if err != nil {
172+
return nil, err
173+
}
174+
imageIndex, err := f.GetInt()
175+
if err != nil {
176+
return nil, err
177+
}
178+
image := images[imageIndex%len(images)]
179+
return image, nil
180+
181+
}
182+
183+
// newContainer creates and returns a container
184+
// The fuzzer decides how the container is created
185+
func newContainer(client *containerd.Client, f *fuzz.ConsumeFuzzer, ctx context.Context) (containerd.Container, error) {
186+
187+
// determiner determines how we should create the container
188+
determiner, err := f.GetInt()
189+
if err != nil {
190+
return nil, err
191+
}
192+
id, err := f.GetString()
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
if determiner%1 == 0 {
198+
// Create a container with oci specs
199+
spec := &oci.Spec{}
200+
err = f.GenerateStruct(spec)
201+
if err != nil {
202+
return nil, err
203+
}
204+
container, err := client.NewContainer(ctx, id,
205+
containerd.WithSpec(spec))
206+
if err != nil {
207+
return nil, err
208+
}
209+
return container, nil
210+
} else if determiner%2 == 0 {
211+
// Create a container with fuzzed oci specs
212+
// and an image
213+
image, err := getImage(client, f)
214+
if err != nil {
215+
return nil, err
216+
}
217+
// Fuzz a few image APIs
218+
_, _ = image.Size(ctx)
219+
checkAndDoUnpack(image, ctx, f)
220+
221+
spec := &oci.Spec{}
222+
err = f.GenerateStruct(spec)
223+
if err != nil {
224+
return nil, err
225+
}
226+
container, err := client.NewContainer(ctx,
227+
id,
228+
containerd.WithImage(image),
229+
containerd.WithSpec(spec))
230+
if err != nil {
231+
return nil, err
232+
}
233+
return container, nil
234+
} else {
235+
// Create a container with an image
236+
image, err := getImage(client, f)
237+
if err != nil {
238+
return nil, err
239+
}
240+
// Fuzz a few image APIs
241+
_, _ = image.Size(ctx)
242+
checkAndDoUnpack(image, ctx, f)
243+
244+
container, err := client.NewContainer(ctx,
245+
id,
246+
containerd.WithImage(image))
247+
if err != nil {
248+
return nil, err
249+
}
250+
return container, nil
251+
}
252+
return nil, errors.New("Could not create container")
253+
}
254+
255+
// FuzzCreateContainer() implements the fuzzer.
256+
// From a high level it:
257+
// - Creates a client
258+
// - Imports a bunch of fuzzed tar archives
259+
// - Creates a bunch of containers
260+
func FuzzCreateContainer(data []byte) int {
261+
ctx, cancel := testContext(nil)
262+
defer cancel()
263+
264+
// Check if daemon is running and start it if it isn't
265+
if ctrd.cmd == nil {
266+
startDaemon(ctx)
267+
}
268+
client, err := containerd.New(address)
269+
if err != nil {
270+
// The error here is most likely with the socket.
271+
// Deleting it will allow the creation of a new
272+
// socket during next fuzz iteration.
273+
deleteSocket()
274+
return -1
275+
}
276+
defer client.Close()
277+
f := fuzz.NewConsumer(data)
278+
279+
// Begin import tars:
280+
noOfImports, err := f.GetInt()
281+
if err != nil {
282+
return 0
283+
}
284+
// maxImports is currently completely arbitrarily defined
285+
maxImports := 30
286+
for i := 0; i < noOfImports%maxImports; i++ {
287+
288+
// f.TarBytes() returns valid tar bytes.
289+
tarBytes, err := f.TarBytes()
290+
if err != nil {
291+
return 0
292+
}
293+
_, _ = client.Import(ctx, bytes.NewReader(tarBytes))
294+
}
295+
// End import tars
296+
297+
// Begin create containers:
298+
existingImages, err := client.ListImages(ctx)
299+
if err != nil {
300+
return 0
301+
}
302+
if len(existingImages) > 0 {
303+
noOfContainers, err := f.GetInt()
304+
if err != nil {
305+
return 0
306+
}
307+
// maxNoOfContainers is currently
308+
// completely arbitrarily defined
309+
maxNoOfContainers := 50
310+
for i := 0; i < noOfContainers%maxNoOfContainers; i++ {
311+
container, err := newContainer(client, f, ctx)
312+
if err == nil {
313+
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
314+
}
315+
}
316+
}
317+
// End create containers
318+
319+
return 1
320+
}

contrib/fuzz/oss_fuzz_build.sh

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,46 @@
1717
cd "$(dirname "${BASH_SOURCE[0]}")"
1818
cd ../../
1919

20-
# Don't move docker_fuzzer.go back into contrib/fuzz
20+
# Move all fuzzers that don't have the "fuzz" package out of this dir
2121
mv contrib/fuzz/docker_fuzzer.go remotes/docker/
22-
compile_go_fuzzer github.com/containerd/containerd/remotes/docker FuzzFetcher fuzz_fetcher
22+
mv contrib/fuzz/container_fuzzer.go integration/client/
23+
2324

25+
compile_go_fuzzer github.com/containerd/containerd/remotes/docker FuzzFetcher fuzz_fetcher
2426
compile_go_fuzzer github.com/containerd/containerd/contrib/fuzz FuzzFiltersParse fuzz_filters_parse
2527
compile_go_fuzzer github.com/containerd/containerd/contrib/fuzz FuzzPlatformsParse fuzz_platforms_parse
2628
compile_go_fuzzer github.com/containerd/containerd/contrib/fuzz FuzzApply fuzz_apply
29+
30+
31+
# FuzzCreateContainer requires more setup than the fuzzers above.
32+
# We need the binaries from "make".
33+
wget -c https://github.com/google/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip
34+
unzip protoc-3.11.4-linux-x86_64.zip -d /usr/local
35+
36+
export CGO_ENABLED=1
37+
export GOARCH=amd64
38+
39+
# Build runc
40+
cd $SRC/
41+
git clone https://github.com/opencontainers/runc
42+
cd runc
43+
make
44+
make install
45+
46+
47+
# Build static containerd
48+
cd $SRC/containerd
49+
make EXTRA_FLAGS="-buildmode pie" \
50+
EXTRA_LDFLAGS='-linkmode external -extldflags "-fno-PIC -static"' \
51+
BUILDTAGS="netgo osusergo static_build"
52+
53+
54+
mkdir $OUT/containerd-binaries || true
55+
cd $SRC/containerd/bin && cp * $OUT/containerd-binaries/ && cd -
56+
57+
cd integration/client
58+
# Rename all *_test.go to *_test_fuzz.go to use their declarations:
59+
for i in $( ls *_test.go ); do mv $i ./${i%.*}_fuzz.go; done
60+
# Remove windows test to avoid double declarations
61+
rm ./client_windows_test_fuzz.go
62+
compile_go_fuzzer . FuzzCreateContainer fuzz_create_container

0 commit comments

Comments
 (0)