diff --git a/Makefile b/Makefile index 395a75127..67f4fb735 100644 --- a/Makefile +++ b/Makefile @@ -276,7 +276,7 @@ docker-tests: load-basic $(RUNTIME_BIN) overlay-tests: load-basic $(RUNTIME_BIN) @$(call install_runtime,$(RUNTIME),--overlay) - @$(call test_runtime,$(RUNTIME),$(INTEGRATION_TARGETS)) + @$(call test_runtime,$(RUNTIME),--test_env=TEST_OVERLAY=true $(INTEGRATION_TARGETS)) .PHONY: overlay-tests swgso-tests: load-basic $(RUNTIME_BIN) @@ -286,7 +286,7 @@ swgso-tests: load-basic $(RUNTIME_BIN) hostnet-tests: load-basic $(RUNTIME_BIN) @$(call install_runtime,$(RUNTIME),--network=host) - @$(call test_runtime,$(RUNTIME),--test_env=CHECKPOINT=false --test_env=HOSTNET=true $(INTEGRATION_TARGETS)) + @$(call test_runtime,$(RUNTIME),--test_env=CHECKPOINT=false --test_env=HOSTNET=true $(INTEGRATION_TARGETS)) .PHONY: hostnet-tests kvm-tests: load-basic $(RUNTIME_BIN) diff --git a/pkg/sentry/fsimpl/gofer/special_file.go b/pkg/sentry/fsimpl/gofer/special_file.go index b92de4fc0..f10050cdc 100644 --- a/pkg/sentry/fsimpl/gofer/special_file.go +++ b/pkg/sentry/fsimpl/gofer/special_file.go @@ -94,7 +94,7 @@ type specialFileFD struct { func newSpecialFileFD(h handle, mnt *vfs.Mount, d *dentry, flags uint32) (*specialFileFD, error) { ftype := d.fileType() seekable := ftype == linux.S_IFREG || ftype == linux.S_IFCHR || ftype == linux.S_IFBLK - haveQueue := (ftype == linux.S_IFIFO || ftype == linux.S_IFSOCK) && h.fd >= 0 + haveQueue := (ftype == linux.S_IFIFO || ftype == linux.S_IFSOCK || ftype == linux.S_IFCHR) && h.fd >= 0 fd := &specialFileFD{ handle: h, isRegularFile: ftype == linux.S_IFREG, diff --git a/pkg/test/testutil/testutil.go b/pkg/test/testutil/testutil.go index 5064b92d7..85e3b0f17 100644 --- a/pkg/test/testutil/testutil.go +++ b/pkg/test/testutil/testutil.go @@ -54,6 +54,8 @@ var ( totalPartitions = flag.Int("total_partitions", IntFromEnv("TOTAL_PARTITIONS", 1), "total number of partitions") isRunningWithHostNet = flag.Bool("hostnet", BoolFromEnv("HOSTNET", false), "whether test is running with hostnet") runscPath = flag.String("runsc", os.Getenv("RUNTIME"), "path to runsc binary") + // Note: flag overlay is already taken by runsc. + isRunningWithOverlay = flag.Bool("test-overlay", BoolFromEnv("TEST_OVERLAY", false), "whether test is running with --overlay") ) // StringFromEnv returns the value of the named environment variable, or `def` if unset/empty. @@ -119,6 +121,11 @@ func IsRunningWithHostNet() bool { return *isRunningWithHostNet } +// IsRunningWithOverlay returns the relevant command line flag. +func IsRunningWithOverlay() bool { + return *isRunningWithOverlay +} + // ImageByName mangles the image name used locally. This depends on the image // build infrastructure in images/ and tools/vm. func ImageByName(name string) string { diff --git a/runsc/fsgofer/fsgofer.go b/runsc/fsgofer/fsgofer.go index d8ea60b0f..80c1e2a45 100644 --- a/runsc/fsgofer/fsgofer.go +++ b/runsc/fsgofer/fsgofer.go @@ -320,7 +320,7 @@ func openAnyFile(pathDebug string, fn func(mode int) (*fd.FD, error)) (*fd.FD, b func checkSupportedFileType(mode uint32, config *Config) error { switch mode & unix.S_IFMT { - case unix.S_IFREG, unix.S_IFDIR, unix.S_IFLNK: + case unix.S_IFREG, unix.S_IFDIR, unix.S_IFLNK, unix.S_IFCHR: return nil case unix.S_IFSOCK: @@ -450,7 +450,7 @@ func (l *localFile) Open(flags p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) { // Best effort to donate file to the Sentry (for performance only). fd = newFDMaybe(newFile) - case unix.S_IFIFO: + case unix.S_IFIFO, unix.S_IFCHR: // Character devices and pipes can block indefinitely during reads/writes, // which is not allowed for gofer operations. Ensure that it donates an FD // back to the caller, so it can wait on the FD when reads/writes return diff --git a/runsc/fsgofer/lisafs.go b/runsc/fsgofer/lisafs.go index 23739b7ec..31802bd6a 100644 --- a/runsc/fsgofer/lisafs.go +++ b/runsc/fsgofer/lisafs.go @@ -427,7 +427,7 @@ func (fd *controlFDLisa) Open(flags uint32) (*lisafs.OpenFD, int, error) { // Best effort to donate file to the Sentry (for performance only). hostOpenFD, _ = unix.Dup(openFD.hostFD) - case unix.S_IFIFO: + case unix.S_IFIFO, unix.S_IFCHR: // Character devices and pipes can block indefinitely during reads/writes, // which is not allowed for gofer operations. Ensure that it donates an FD // back to the caller, so it can wait on the FD when reads/writes return diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index 8caf32eb8..a56b1cc5b 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -22,6 +22,7 @@ package integration import ( + "bytes" "context" "flag" "fmt" @@ -997,3 +998,51 @@ func TestNonSearchableWorkingDirectory(t *testing.T) { t.Errorf("ls error message not found, want: %q, got: %q", wantErrorMsg, got) } } + +func TestCharDevice(t *testing.T) { + if testutil.IsRunningWithOverlay() { + t.Skip("files are not available outside the sandbox with overlay.") + } + + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + dir, err := os.MkdirTemp(testutil.TmpDir(), "tmp-mount") + if err != nil { + t.Fatalf("MkdirTemp() failed: %v", err) + } + defer os.RemoveAll(dir) + + opts := dockerutil.RunOpts{ + Image: "basic/alpine", + Mounts: []mount.Mount{ + { + Type: mount.TypeBind, + Source: "/dev/zero", + Target: "/test/zero", + }, + { + Type: mount.TypeBind, + Source: dir, + Target: "/out", + }, + }, + } + + const size = 1024 * 1024 + + // `docker logs` encodes the string, making it hard to compare. Write the + // result to a file that is available to the test. + cmd := fmt.Sprintf("head -c %d /test/zero > /out/result", size) + if _, err := d.Run(ctx, opts, "sh", "-c", cmd); err != nil { + t.Fatalf("docker run failed: %v", err) + } + got, err := os.ReadFile(filepath.Join(dir, "result")) + if err != nil { + t.Fatal(err) + } + if want := [size]byte{}; !bytes.Equal(want[:], got) { + t.Errorf("Wrong bytes, want: [all zeros], got: %v", got) + } +}