diff options
| author | Florent Revest <revest@chromium.org> | 2024-11-28 01:50:23 +0100 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2024-12-09 18:35:48 +0000 |
| commit | deb728774249ce479316c219f77530e2af52e3bd (patch) | |
| tree | 0c40542088d8ffebf4ee5ddb56a61a94ed57afaf /pkg | |
| parent | 07e46fbc2bd7ff8782c975596672e4e3d3891865 (diff) | |
prog: annotate image assets with fsck logs
Syscall attributes are extended with a fsck command field which lets
file system mount definitions specify a fsck-like command to run. This
is required because all file systems have a custom fsck command
invokation style.
When uploading a compressed image asset to the dashboard, syz-manager
also runs the fsck command and logs its output over the dashapi.
The dashboard logs these fsck logs into the database.
This has been requested by fs maintainer Ted Tso who would like to
quickly understand whether a filesystem is corrupted or not before
looking at a reproducer in more details. Ultimately, this could be used
as an early triage sign to determine whether a bug is obviously
critical.
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/compiler/testdata/all.txt | 4 | ||||
| -rw-r--r-- | pkg/compiler/testdata/errors.txt | 2 | ||||
| -rw-r--r-- | pkg/image/fsck.go | 59 | ||||
| -rw-r--r-- | pkg/image/fsck_test.go | 93 | ||||
| -rw-r--r-- | pkg/manager/crash.go | 2 | ||||
| -rw-r--r-- | pkg/mgrconfig/config.go | 7 |
6 files changed, 166 insertions, 1 deletions
diff --git a/pkg/compiler/testdata/all.txt b/pkg/compiler/testdata/all.txt index b19b85980..1202e7511 100644 --- a/pkg/compiler/testdata/all.txt +++ b/pkg/compiler/testdata/all.txt @@ -336,6 +336,10 @@ struct$fmt0 { flags_with_one_value = 0 +# Syscall attributes. + +fsck_test() (fsck["fsck.test -n"]) + # Compressed images. struct_compressed { diff --git a/pkg/compiler/testdata/errors.txt b/pkg/compiler/testdata/errors.txt index 4f3186698..dc9170315 100644 --- a/pkg/compiler/testdata/errors.txt +++ b/pkg/compiler/testdata/errors.txt @@ -507,3 +507,5 @@ conditional_fields_union2 [ u2 int32 ### either no fields have conditions or all except the last u3 int32 ] + +invalid_string_attr() (invalid["string"]) ### unknown syscall invalid_string_attr attribute invalid
\ No newline at end of file diff --git a/pkg/image/fsck.go b/pkg/image/fsck.go new file mode 100644 index 000000000..4c619b828 --- /dev/null +++ b/pkg/image/fsck.go @@ -0,0 +1,59 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package image + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "strconv" + "strings" + + "github.com/google/syzkaller/pkg/osutil" +) + +// Fsck runs fsckCmd against a file system image provided in r. It returns the +// fsck logs, whether the file system is clean and an error in case fsck could +// not be run. +func Fsck(r io.Reader, fsckCmd string) ([]byte, bool, error) { + // Write the image to a temporary file. + tempFile, err := os.CreateTemp("", "*.img") + if err != nil { + return nil, false, fmt.Errorf("failed to create temporary file: %w", err) + } + defer os.Remove(tempFile.Name()) + + _, err = io.Copy(tempFile, r) + if err != nil { + return nil, false, fmt.Errorf("failed to write data to temporary file: %w", err) + } + + if err := tempFile.Close(); err != nil { + return nil, false, fmt.Errorf("failed to close temporary file: %w", err) + } + + // And run the provided fsck command on it. + fsck := append(strings.Fields(fsckCmd), tempFile.Name()) + cmd := osutil.Command(fsck[0], fsck[1:]...) + if err := osutil.Sandbox(cmd, true, true); err != nil { + return nil, false, err + } + + exitCode := 0 + output, err := cmd.CombinedOutput() + if err != nil { + var exitError (*exec.ExitError) + ok := errors.As(err, &exitError) + if ok { + exitCode = exitError.ExitCode() + } else { + return nil, false, err + } + } + + prefix := fsckCmd + " exited with status code " + strconv.Itoa(exitCode) + "\n" + return append([]byte(prefix), output...), exitCode == 0, nil +} diff --git a/pkg/image/fsck_test.go b/pkg/image/fsck_test.go new file mode 100644 index 000000000..aae102ec7 --- /dev/null +++ b/pkg/image/fsck_test.go @@ -0,0 +1,93 @@ +// Copyright 2024 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package image_test + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + . "github.com/google/syzkaller/pkg/image" + "github.com/google/syzkaller/prog" + "github.com/google/syzkaller/sys/targets" +) + +// To get maximum test coverage here, install the following Debian packages: +// dosfstools e2fsprogs btrfs-progs util-linux f2fs-tools jfsutils util-linux +// dosfstools ocfs2-tools reiserfsprogs xfsprogs erofs-utils exfatprogs +// gfs2-utils. + +const corruptedFs = "IAmACorruptedFs" + +func fsckAvailable(cmd string) bool { + _, err := exec.LookPath(strings.Fields(cmd)[0]) + return err == nil +} + +func TestFsck(t *testing.T) { + target, err := prog.GetTarget(targets.Linux, targets.AMD64) + if err != nil { + t.Fatal(err) + } + + // Use the images generated by syz-imagegen as a collection of clean file systems. + cleanFsProgs, err := filepath.Glob(filepath.Join("..", "sys", "linux", "test", "syz_mount_image_*_0")) + if err != nil { + t.Fatalf("directory read failed: %v", err) + } + + for _, file := range cleanFsProgs { + sourceProg, err := os.ReadFile(file) + if err != nil { + t.Fatal(err) + } + p, err := target.Deserialize(sourceProg, prog.NonStrict) + if err != nil { + t.Fatalf("failed to deserialize %s: %s", file, err) + } + p.ForEachAsset(func(name string, typ prog.AssetType, r io.Reader, c *prog.Call) { + if c.Meta.Attrs.Fsck == "" { + return + } + fsckCmd := c.Meta.Attrs.Fsck + // Tolerate missing fsck commands except during CI runs. + skip := !fsckAvailable(fsckCmd) && os.Getenv("CI") == "" + + fsName := strings.TrimPrefix(c.Meta.Name, "syz_mount_image$") + // Check that the file system in the image is detected as clean. + t.Run(fmt.Sprintf("clean %s", fsName), func(t *testing.T) { + if skip { + t.Skipf("%s not available", fsckCmd) + } + + logs, isClean, err := Fsck(r, fsckCmd) + if err != nil { + t.Fatalf("failed to run fsck %s", err) + } + if !isClean { + t.Fatalf("%s should exit 0 on a clean file system %s", fsckCmd, string(logs)) + } + }) + + // And use the same fsck command on a dummy fs to make sure that fails. + t.Run(fmt.Sprintf("corrupt %s", fsName), func(t *testing.T) { + if skip { + t.Skipf("%s not available", fsckCmd) + } + + logs, isClean, err := Fsck(strings.NewReader(corruptedFs), fsckCmd) + if err != nil { + t.Fatalf("failed to run fsck %s", err) + } + if isClean { + t.Fatalf("%s shouldn't exit 0 on a corrupt file system %s", fsckCmd, string(logs)) + } + }) + }) + } +} diff --git a/pkg/manager/crash.go b/pkg/manager/crash.go index 56142695a..d995b5633 100644 --- a/pkg/manager/crash.go +++ b/pkg/manager/crash.go @@ -148,7 +148,7 @@ func (cs *CrashStore) SaveRepro(res *ReproResult, progText, cProgText []byte) er osutil.WriteFile(filepath.Join(dir, cReproFileName), cProgText) } var assetErr error - repro.Prog.ForEachAsset(func(name string, typ prog.AssetType, r io.Reader) { + repro.Prog.ForEachAsset(func(name string, typ prog.AssetType, r io.Reader, c *prog.Call) { fileName := filepath.Join(dir, name+".gz") if err := osutil.WriteGzipStream(fileName, r); err != nil { assetErr = fmt.Errorf("failed to write crash asset: type %d, %w", typ, err) diff --git a/pkg/mgrconfig/config.go b/pkg/mgrconfig/config.go index b475c4eed..1e499412a 100644 --- a/pkg/mgrconfig/config.go +++ b/pkg/mgrconfig/config.go @@ -192,6 +192,13 @@ type Config struct { // the output. StraceBin string `json:"strace_bin"` + // Whether to run fsck commands on file system images found in new crash + // reproducers. The fsck logs get reported as assets in the dashboard. + // Note: you may need to install 3rd-party dependencies for this to work. + // fsck commands that can be run by syz-manager are specified in mount + // syscall descriptions - typically in sys/linux/filesystem.txt. + RunFsck bool `json:"run_fsck"` + // Type of virtual machine to use, e.g. "qemu", "gce", "android", "isolated", etc. Type string `json:"type"` // VM-type-specific parameters. |
