github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/layer/layer_fuzzer.go (about)

     1  //go:build gofuzz
     2  // +build gofuzz
     3  
     4  /*
     5   * umoci: Umoci Modifies Open Containers' Images
     6   * Copyright (C) 2021 SUSE LLC
     7   *
     8   * Licensed under the Apache License, Version 2.0 (the "License");
     9   * you may not use this file except in compliance with the License.
    10   * You may obtain a copy of the License at
    11   *
    12   *    http://www.apache.org/licenses/LICENSE-2.0
    13   *
    14   * Unless required by applicable law or agreed to in writing, software
    15   * distributed under the License is distributed on an "AS IS" BASIS,
    16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    17   * See the License for the specific language governing permissions and
    18   * limitations under the License.
    19   */
    20  
    21  package layer
    22  
    23  import (
    24  	"bytes"
    25  	"encoding/base64"
    26  	"io"
    27  
    28  	"github.com/opencontainers/go-digest"
    29  	"github.com/opencontainers/umoci/oci/cas/dir"
    30  
    31  	"context"
    32  
    33  	"github.com/apex/log"
    34  	"github.com/opencontainers/image-spec/specs-go"
    35  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    36  	rspec "github.com/opencontainers/runtime-spec/specs-go"
    37  	"github.com/opencontainers/umoci/oci/casext"
    38  
    39  	"archive/tar"
    40  
    41  	"io/ioutil"
    42  	"os"
    43  	"path/filepath"
    44  	"unicode"
    45  
    46  	fuzz "github.com/AdaLogics/go-fuzz-headers"
    47  	fuzzheaders "github.com/AdaLogics/go-fuzz-headers"
    48  	"github.com/vbatts/go-mtree"
    49  )
    50  
    51  // createRandomFile is a helper function
    52  func createRandomFile(dirpath string, filename []byte, filecontents []byte) error {
    53  	fileP := filepath.Join(dirpath, string(filename))
    54  	if err := ioutil.WriteFile(fileP, filecontents, 0644); err != nil {
    55  		return err
    56  	}
    57  	defer os.Remove(fileP)
    58  	return nil
    59  }
    60  
    61  // createRandomDir is a helper function
    62  func createRandomDir(basedir string, dirname []byte, dirArray []string) ([]string, error) {
    63  	dirPath := filepath.Join(basedir, string(dirname))
    64  	if err := os.MkdirAll(dirPath, 0755); err != nil {
    65  		return dirArray, err
    66  	}
    67  	defer os.RemoveAll(dirPath)
    68  	dirArray = append(dirArray, string(dirname))
    69  	return dirArray, nil
    70  }
    71  
    72  // isLetter is a helper function
    73  func isLetter(input []byte) bool {
    74  	s := string(input)
    75  	for _, r := range s {
    76  		if !unicode.IsLetter(r) {
    77  			return false
    78  		}
    79  	}
    80  	return true
    81  }
    82  
    83  // FuzzGenerateLayer implements a fuzzer
    84  // that targets layer.GenerateLayer().
    85  func FuzzGenerateLayer(data []byte) int {
    86  	if len(data) < 5 {
    87  		return -1
    88  	}
    89  	if !fuzzheaders.IsDivisibleBy(len(data), 2) {
    90  		return -1
    91  	}
    92  	half := len(data) / 2
    93  	firstHalf := data[:half]
    94  	f1 := fuzzheaders.NewConsumer(firstHalf)
    95  	err := f1.Split(3, 30)
    96  	if err != nil {
    97  		return -1
    98  	}
    99  
   100  	secondHalf := data[half:]
   101  	f2 := fuzzheaders.NewConsumer(secondHalf)
   102  	err = f2.Split(3, 30)
   103  	if err != nil {
   104  		return -1
   105  	}
   106  	baseDir := "fuzz-base-dir"
   107  	if err := os.MkdirAll(baseDir, 0755); err != nil {
   108  		return -1
   109  	}
   110  	defer os.RemoveAll(baseDir)
   111  
   112  	var dirArray []string
   113  	iteration := 0
   114  	chunkSize := len(f1.RestOfArray) / f1.NumberOfCalls
   115  	for i := 0; i < len(f1.RestOfArray); i = i + chunkSize {
   116  		from := i           //lower
   117  		to := i + chunkSize //upper
   118  		inputData := firstHalf[from:to]
   119  		if len(inputData) > 6 && isLetter(inputData[:5]) {
   120  			dirArray, err = createRandomDir(baseDir, inputData[:5], dirArray)
   121  			if err != nil {
   122  				continue
   123  			}
   124  		} else {
   125  			if len(dirArray) == 0 {
   126  				continue
   127  			}
   128  			dirp := int(inputData[0]) % len(dirArray)
   129  			fp := filepath.Join(baseDir, dirArray[dirp])
   130  			if len(inputData) > 10 {
   131  				filename := inputData[5:8]
   132  				err = createRandomFile(fp, filename, inputData[8:])
   133  				if err != nil {
   134  					continue
   135  				}
   136  			}
   137  		}
   138  		iteration++
   139  	}
   140  
   141  	// Get initial.
   142  	initDh, err := mtree.Walk(baseDir, nil, append(mtree.DefaultKeywords, "sha256digest"), nil)
   143  	if err != nil {
   144  		return 0
   145  	}
   146  	iteration = 0
   147  	chunkSize = len(f2.RestOfArray) / f2.NumberOfCalls
   148  	for i := 0; i < len(f2.RestOfArray); i = i + chunkSize {
   149  		from := i           //lower
   150  		to := i + chunkSize //upper
   151  		inputData := secondHalf[from:to]
   152  		if len(inputData) > 6 && isLetter(inputData[:5]) {
   153  			dirArray, err = createRandomDir(baseDir, inputData[:5], dirArray)
   154  			if err != nil {
   155  				continue
   156  			}
   157  		} else {
   158  			if len(dirArray) == 0 {
   159  				continue
   160  			}
   161  			dirp := int(inputData[0]) % len(dirArray)
   162  			fp := filepath.Join(baseDir, dirArray[dirp])
   163  			if len(inputData) > 10 {
   164  				filename := inputData[5:8]
   165  				err = createRandomFile(fp, filename, inputData[8:])
   166  				if err != nil {
   167  					continue
   168  				}
   169  			}
   170  		}
   171  		iteration++
   172  	}
   173  
   174  	// Get post.
   175  	postDh, err := mtree.Walk(baseDir, nil, initDh.UsedKeywords(), nil)
   176  	if err != nil {
   177  		return 0
   178  	}
   179  
   180  	diffs, err := mtree.Compare(initDh, postDh, initDh.UsedKeywords())
   181  	if err != nil {
   182  		return -1
   183  	}
   184  	reader, err := GenerateLayer(baseDir, diffs, &RepackOptions{})
   185  	if err != nil {
   186  		return -1
   187  	}
   188  	defer reader.Close()
   189  
   190  	tr := tar.NewReader(reader)
   191  	for {
   192  		_, err = tr.Next()
   193  		if err != nil {
   194  			break
   195  		}
   196  	}
   197  	return 1
   198  }
   199  
   200  // mustDecodeString is a helper function
   201  func mustDecodeString(s string) []byte {
   202  	b, _ := base64.StdEncoding.DecodeString(s)
   203  	return b
   204  }
   205  
   206  // makeImage is a helper function
   207  func makeImage(base641, base642 string) (string, ispec.Manifest, casext.Engine, error) {
   208  
   209  	ctx := context.Background()
   210  
   211  	var layers = []struct {
   212  		base64 string
   213  		digest digest.Digest
   214  	}{
   215  		{
   216  			base64: base641,
   217  			digest: digest.NewDigestFromHex(digest.SHA256.String(), "e489a16a8ca0d682394867ad8a8183f0a47cbad80b3134a83412a6796ad9242a"),
   218  		},
   219  		{
   220  			base64: base642,
   221  			digest: digest.NewDigestFromHex(digest.SHA256.String(), "39f100ed000b187ba74b3132cc207c63ad1765adaeb783aa7f242f1f7b6f5ea2"),
   222  		},
   223  	}
   224  
   225  	root, err := ioutil.TempDir("", "umoci-TestUnpackManifestCustomLayer")
   226  	if err != nil {
   227  		return "nil", ispec.Manifest{}, casext.Engine{}, err
   228  	}
   229  
   230  	// Create our image.
   231  	image := filepath.Join(root, "image")
   232  	if err := dir.Create(image); err != nil {
   233  		return "nil", ispec.Manifest{}, casext.Engine{}, err
   234  	}
   235  	engine, err := dir.Open(image)
   236  	if err != nil {
   237  		return "nil", ispec.Manifest{}, casext.Engine{}, err
   238  	}
   239  	engineExt := casext.NewEngine(engine)
   240  
   241  	// Set up the CAS and an image from the above layers.
   242  	var layerDigests []digest.Digest
   243  	var layerDescriptors []ispec.Descriptor
   244  	for _, layer := range layers {
   245  		var layerReader io.Reader
   246  
   247  		layerReader = bytes.NewBuffer(mustDecodeString(layer.base64))
   248  		layerDigest, layerSize, err := engineExt.PutBlob(ctx, layerReader)
   249  		if err != nil {
   250  			return "nil", ispec.Manifest{}, casext.Engine{}, err
   251  		}
   252  
   253  		layerDigests = append(layerDigests, layer.digest)
   254  		layerDescriptors = append(layerDescriptors, ispec.Descriptor{
   255  			MediaType: ispec.MediaTypeImageLayerGzip,
   256  			Digest:    layerDigest,
   257  			Size:      layerSize,
   258  		})
   259  	}
   260  
   261  	// Create the config.
   262  	config := ispec.Image{
   263  		OS: "linux",
   264  		RootFS: ispec.RootFS{
   265  			Type:    "layers",
   266  			DiffIDs: layerDigests,
   267  		},
   268  	}
   269  	configDigest, configSize, err := engineExt.PutBlobJSON(ctx, config)
   270  	if err != nil {
   271  		return "nil", ispec.Manifest{}, casext.Engine{}, err
   272  	}
   273  	configDescriptor := ispec.Descriptor{
   274  		MediaType: ispec.MediaTypeImageConfig,
   275  		Digest:    configDigest,
   276  		Size:      configSize,
   277  	}
   278  
   279  	// Create the manifest.
   280  	manifest := ispec.Manifest{
   281  		Versioned: specs.Versioned{
   282  			SchemaVersion: 2,
   283  		},
   284  		MediaType: ispec.MediaTypeImageManifest,
   285  		Config:    configDescriptor,
   286  		Layers:    layerDescriptors,
   287  	}
   288  
   289  	return root, manifest, engineExt, nil
   290  }
   291  
   292  // FuzzUnpack implements a fuzzer
   293  // that targets UnpackManifest().
   294  func FuzzUnpack(data []byte) int {
   295  	// We would like as little log output as possible:
   296  	level, err := log.ParseLevel("fatal")
   297  	if err != nil {
   298  		return -1
   299  	}
   300  	log.SetLevel(level)
   301  	ctx := context.Background()
   302  	c := fuzz.NewConsumer(data)
   303  	base641, err := c.GetString()
   304  	if err != nil {
   305  		return -1
   306  	}
   307  
   308  	base642, err := c.GetString()
   309  	if err != nil {
   310  		return -1
   311  	}
   312  	root, manifest, engineExt, err := makeImage(base641, base642)
   313  	if err != nil {
   314  		return 0
   315  	}
   316  	defer os.RemoveAll(root)
   317  
   318  	bundle, err := ioutil.TempDir("", "umoci-TestUnpackManifestCustomLayer_bundle")
   319  	if err != nil {
   320  		return 0
   321  	}
   322  	defer os.RemoveAll(bundle)
   323  
   324  	unpackOptions := &UnpackOptions{MapOptions: MapOptions{
   325  		UIDMappings: []rspec.LinuxIDMapping{
   326  			{HostID: uint32(os.Geteuid()), ContainerID: 0, Size: 1},
   327  			{HostID: uint32(os.Geteuid()), ContainerID: 1000, Size: 1},
   328  		},
   329  		GIDMappings: []rspec.LinuxIDMapping{
   330  			{HostID: uint32(os.Getegid()), ContainerID: 0, Size: 1},
   331  			{HostID: uint32(os.Getegid()), ContainerID: 100, Size: 1},
   332  		},
   333  		Rootless: os.Geteuid() != 0,
   334  	}}
   335  
   336  	called := false
   337  	unpackOptions.AfterLayerUnpack = func(m ispec.Manifest, d ispec.Descriptor) error {
   338  		called = true
   339  		return nil
   340  	}
   341  
   342  	_ = UnpackManifest(ctx, engineExt, bundle, manifest, unpackOptions)
   343  
   344  	return 1
   345  }