diff --git a/pkg/sentry/fsimpl/proc/filesystem.go b/pkg/sentry/fsimpl/proc/filesystem.go index 6cf643b192..081f1c583b 100644 --- a/pkg/sentry/fsimpl/proc/filesystem.go +++ b/pkg/sentry/fsimpl/proc/filesystem.go @@ -164,6 +164,11 @@ type InternalData struct { // should exist in the procfs. GVisorMarkerFile bool + // OverrideProcs is a list of proc files to override with stubs. The + // /proc/ prefix is removed (e.g., this contains filenames like + // "kallsyms"). + OverrideProcs []string + // Cgroups-internal data. Cgroups map[string]string } diff --git a/pkg/sentry/fsimpl/proc/tasks.go b/pkg/sentry/fsimpl/proc/tasks.go index 02a7b954e6..129cf56a80 100644 --- a/pkg/sentry/fsimpl/proc/tasks.go +++ b/pkg/sentry/fsimpl/proc/tasks.go @@ -103,6 +103,10 @@ func (fs *filesystem) newTasksInode(ctx context.Context, k *kernel.Kernel, pidns } fs.addNvproxyFiles(ctx, root, k, contents) + for _, name := range internalData.OverrideProcs { + contents[name] = fs.newInode(ctx, root, 0444, newStaticFile("")) + } + fs.newTasksInodeExtra(ctx, root, internalData, k, contents) inode := &tasksInode{ diff --git a/pkg/sentry/fsimpl/proc/tasks_test.go b/pkg/sentry/fsimpl/proc/tasks_test.go index fc96bb815f..8f4e38ff10 100644 --- a/pkg/sentry/fsimpl/proc/tasks_test.go +++ b/pkg/sentry/fsimpl/proc/tasks_test.go @@ -16,6 +16,7 @@ package proc import ( "fmt" + "io" "math" "path" "strconv" @@ -105,6 +106,15 @@ var ( ) func setup(t *testing.T) *testutil.System { + return setupWithData(t, &InternalData{ + Cgroups: map[string]string{ + "cpuset": "/foo/cpuset", + "memory": "/foo/memory", + }, + }) +} + +func setupWithData(t *testing.T, data *InternalData) *testutil.System { k, err := testutil.Boot() if err != nil { t.Fatalf("Error creating kernel: %v", err) @@ -139,12 +149,7 @@ func setup(t *testing.T) *testutil.System { } mntOpts := &vfs.MountOptions{ GetFilesystemOptions: vfs.GetFilesystemOptions{ - InternalData: &InternalData{ - Cgroups: map[string]string{ - "cpuset": "/foo/cpuset", - "memory": "/foo/memory", - }, - }, + InternalData: data, }, } if _, err := k.VFS().MountAt(ctx, creds, "", pop, Name, mntOpts); err != nil { @@ -162,6 +167,52 @@ func TestTasksEmpty(t *testing.T) { s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs) } +func TestTasksWithOverrideProc(t *testing.T) { + s := setupWithData(t, &InternalData{ + Cgroups: map[string]string{ + "cpuset": "/foo/cpuset", + "memory": "/foo/memory", + }, + OverrideProcs: []string{"kallsyms", "arbitrary_file"}, + }) + defer s.Destroy() + + expected := make(map[string]testutil.DirentType) + for k, v := range tasksStaticFiles { + expected[k] = v + } + expected["kallsyms"] = linux.DT_REG + expected["arbitrary_file"] = linux.DT_REG + + collector := s.ListDirents(s.PathOpAtRoot("/proc")) + s.AssertAllDirentTypes(collector, expected) + + // Verify file contents are empty. + for _, name := range []string{"kallsyms", "arbitrary_file"} { + filePath := path.Join("/proc", name) + fd, err := s.VFS.OpenAt( + s.Ctx, + s.Creds, + s.PathOpAtRoot(filePath), + &vfs.OpenOptions{}, + ) + if err != nil { + t.Fatalf("vfs.OpenAt(%q) failed: %v", filePath, err) + } + defer fd.DecRef(s.Ctx) + + buf := make([]byte, 1024) + bufIOSeq := usermem.BytesIOSequence(buf) + n, err := fd.Read(s.Ctx, bufIOSeq, vfs.ReadOptions{}) + if err != nil && err != io.EOF { + t.Errorf("read %q failed: %v", filePath, err) + } + if n != 0 { + t.Errorf("expected empty file %q, got %d bytes: %q", filePath, n, buf[:n]) + } + } +} + func TestTasks(t *testing.T) { s := setup(t) defer s.Destroy() diff --git a/runsc/boot/restore.go b/runsc/boot/restore.go index e93c4df24c..068a5cd7e4 100644 --- a/runsc/boot/restore.go +++ b/runsc/boot/restore.go @@ -20,6 +20,7 @@ import ( "io" "os" "strconv" + "strings" time2 "time" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -524,3 +525,15 @@ func (l *Loader) saveWithOpts(saveOpts *state.SaveOpts, execOpts *control.SaveRe } return state.SaveWithOpts(saveOpts, execOpts) } + +func procFiles(conf *config.Config) []string { + var files []string + + if conf.OverrideProcs != "" { + for _, val := range strings.Split(conf.OverrideProcs, ",") { + files = append(files, strings.TrimPrefix(val, "/proc/")) + } + } + + return files +} diff --git a/runsc/boot/restore_impl.go b/runsc/boot/restore_impl.go index 9ae176085f..c5285035ca 100644 --- a/runsc/boot/restore_impl.go +++ b/runsc/boot/restore_impl.go @@ -28,6 +28,7 @@ import ( func newProcInternalData(conf *config.Config, _ *specs.Spec) *proc.InternalData { return &proc.InternalData{ GVisorMarkerFile: conf.GVisorMarkerFile, + OverrideProcs: procFiles(conf), } } diff --git a/runsc/config/config.go b/runsc/config/config.go index fd6268015d..366e2a20d1 100644 --- a/runsc/config/config.go +++ b/runsc/config/config.go @@ -406,6 +406,9 @@ type Config struct { // GVisorMarkerFile enables the /proc/gvisor/kernel_is_gvisor marker file. GVisorMarkerFile bool `flag:"gvisor-marker-file"` + // OverrideProcs is a comma-separated list of proc files to override with stubs. + OverrideProcs string `flag:"override-procs"` + // SystrapDisableSyscallPatching disables syscall patching in Systrap. SystrapDisableSyscallPatching bool `flag:"systrap-disable-syscall-patching"` diff --git a/runsc/config/flags.go b/runsc/config/flags.go index 6d7e3714c3..c86e3ebb6a 100644 --- a/runsc/config/flags.go +++ b/runsc/config/flags.go @@ -139,6 +139,7 @@ func RegisterFlags(flagSet *flag.FlagSet) { flagSet.Var(hostUDSPtr(HostUDSNone), flagHostUDS, "controls permission to access host Unix-domain sockets. Values: none|open|create|all, default: none") flagSet.Var(hostFifoPtr(HostFifoNone), "host-fifo", "controls permission to access host FIFOs (or named pipes). Values: none|open, default: none") flagSet.Bool("gvisor-marker-file", false, "enable the presence of the /proc/gvisor/kernel_is_gvisor file that can be used by applications to detect that gVisor is in use") + flagSet.String("override-procs", "", "comma-separated list of proc files to override with stubs (e.g. kallsyms)") flagSet.Bool("ignore-cgroups", false, "don't configure cgroups.") flagSet.Int("fdlimit", -1, "Specifies a limit on the number of host file descriptors that can be open. Applies separately to the sentry and gofer. Note: each file in the sandbox holds more than one host FD open.")