Fix problem with open FD when copy up is triggered in overlayfs

Linux kernel before 4.19 doesn't implement a feature that updates
open FD after a file is open for write (and is copied to the upper
layer). Already open FD will continue to read the old file content
until they are reopened. This is especially problematic for gVisor
because it caches open files.

Flag was added to force readonly files to be reopenned when the
same file is open for write. This is only needed if using kernels
prior to 4.19.

Closes #1006

It's difficult to really test this because we never run on tests
on older kernels. I'm adding a test in GKE which uses kernels
with the overlayfs problem for 1.14 and lower.

PiperOrigin-RevId: 275115289
This commit is contained in:
Fabricio Voznika 2019-10-16 14:33:23 -07:00 committed by gVisor bot
parent fd4e436002
commit 9fb562234e
15 changed files with 287 additions and 34 deletions

View File

@ -209,3 +209,29 @@ func (f *HostFileMapper) unmapAndRemoveLocked(chunkStart uint64, m mapping) {
}
delete(f.mappings, chunkStart)
}
// RegenerateMappings must be called when the file description mapped by f
// changes, to replace existing mappings of the previous file description.
func (f *HostFileMapper) RegenerateMappings(fd int) error {
f.mapsMu.Lock()
defer f.mapsMu.Unlock()
for chunkStart, m := range f.mappings {
prot := syscall.PROT_READ
if m.writable {
prot |= syscall.PROT_WRITE
}
_, _, errno := syscall.Syscall6(
syscall.SYS_MMAP,
m.addr,
chunkSize,
uintptr(prot),
syscall.MAP_SHARED|syscall.MAP_FIXED,
uintptr(fd),
uintptr(chunkStart))
if errno != 0 {
return errno
}
}
return nil
}

View File

@ -100,13 +100,30 @@ func (h *HostMappable) Translate(ctx context.Context, required, optional memmap.
}
// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable.
func (h *HostMappable) InvalidateUnsavable(ctx context.Context) error {
func (h *HostMappable) InvalidateUnsavable(_ context.Context) error {
h.mu.Lock()
h.mappings.InvalidateAll(memmap.InvalidateOpts{})
h.mu.Unlock()
return nil
}
// NotifyChangeFD must be called after the file description represented by
// CachedFileObject.FD() changes.
func (h *HostMappable) NotifyChangeFD() error {
// Update existing sentry mappings to refer to the new file description.
if err := h.hostFileMapper.RegenerateMappings(h.backingFile.FD()); err != nil {
return err
}
// Shoot down existing application mappings of the old file description;
// they will be remapped with the new file description on demand.
h.mu.Lock()
defer h.mu.Unlock()
h.mappings.InvalidateAll(memmap.InvalidateOpts{})
return nil
}
// MapInternal implements platform.File.MapInternal.
func (h *HostMappable) MapInternal(fr platform.FileRange, at usermem.AccessType) (safemem.BlockSeq, error) {
return h.hostFileMapper.MapInternal(fr, h.backingFile.FD(), at.Write)

View File

@ -959,6 +959,23 @@ func (c *CachingInodeOperations) InvalidateUnsavable(ctx context.Context) error
return nil
}
// NotifyChangeFD must be called after the file description represented by
// CachedFileObject.FD() changes.
func (c *CachingInodeOperations) NotifyChangeFD() error {
// Update existing sentry mappings to refer to the new file description.
if err := c.hostFileMapper.RegenerateMappings(c.backingFile.FD()); err != nil {
return err
}
// Shoot down existing application mappings of the old file description;
// they will be remapped with the new file description on demand.
c.mapsMu.Lock()
defer c.mapsMu.Unlock()
c.mappings.InvalidateAll(memmap.InvalidateOpts{})
return nil
}
// Evict implements pgalloc.EvictableMemoryUser.Evict.
func (c *CachingInodeOperations) Evict(ctx context.Context, er pgalloc.EvictableRange) {
c.mapsMu.Lock()
@ -1027,7 +1044,6 @@ func (c *CachingInodeOperations) DecRef(fr platform.FileRange) {
}
c.refs.MergeAdjacent(fr)
c.dataMu.Unlock()
}
// MapInternal implements platform.File.MapInternal. This is used when we

View File

@ -29,7 +29,7 @@ func (f *fileOperations) afterLoad() {
// Manually load the open handles.
var err error
// TODO(b/38173783): Context is not plumbed to save/restore.
f.handles, err = f.inodeOperations.fileState.getHandles(context.Background(), f.flags)
f.handles, err = f.inodeOperations.fileState.getHandles(context.Background(), f.flags, f.inodeOperations.cachingInodeOps)
if err != nil {
return fmt.Errorf("failed to re-open handle: %v", err)
}

View File

@ -58,6 +58,11 @@ const (
// If present, sets CachingInodeOperationsOptions.LimitHostFDTranslation to
// true.
limitHostFDTranslationKey = "limit_host_fd_translation"
// overlayfsStaleRead if present closes cached readonly file after the first
// write. This is done to workaround a limitation of overlayfs in kernels
// before 4.19 where open FDs are not updated after the file is copied up.
overlayfsStaleRead = "overlayfs_stale_read"
)
// defaultAname is the default attach name.
@ -145,6 +150,7 @@ type opts struct {
version string
privateunixsocket bool
limitHostFDTranslation bool
overlayfsStaleRead bool
}
// options parses mount(2) data into structured options.
@ -247,6 +253,11 @@ func options(data string) (opts, error) {
delete(options, limitHostFDTranslationKey)
}
if _, ok := options[overlayfsStaleRead]; ok {
o.overlayfsStaleRead = true
delete(options, overlayfsStaleRead)
}
// Fail to attach if the caller wanted us to do something that we
// don't support.
if len(options) > 0 {

View File

@ -39,14 +39,22 @@ type handles struct {
// Host is an *fd.FD handle. May be nil.
Host *fd.FD
// isHostBorrowed tells whether 'Host' is owned or borrowed. If owned, it's
// closed on destruction, otherwise it's released.
isHostBorrowed bool
}
// DecRef drops a reference on handles.
func (h *handles) DecRef() {
h.DecRefWithDestructor(func() {
if h.Host != nil {
if err := h.Host.Close(); err != nil {
log.Warningf("error closing host file: %v", err)
if h.isHostBorrowed {
h.Host.Release()
} else {
if err := h.Host.Close(); err != nil {
log.Warningf("error closing host file: %v", err)
}
}
}
// FIXME(b/38173783): Context is not plumbed here.

View File

@ -100,7 +100,7 @@ type inodeFileState struct {
// true.
//
// Once readHandles becomes non-nil, it can't be changed until
// inodeFileState.Release(), because of a defect in the
// inodeFileState.Release()*, because of a defect in the
// fsutil.CachedFileObject interface: there's no way for the caller of
// fsutil.CachedFileObject.FD() to keep the returned FD open, so if we
// racily replace readHandles after inodeFileState.FD() has returned
@ -108,6 +108,9 @@ type inodeFileState struct {
// FD. writeHandles can be changed if writeHandlesRW is false, since
// inodeFileState.FD() can't return a write-only FD, but can't be changed
// if writeHandlesRW is true for the same reason.
//
// * There is one notable exception in recreateReadHandles(), where it dup's
// the FD and invalidates the page cache.
readHandles *handles `state:"nosave"`
writeHandles *handles `state:"nosave"`
writeHandlesRW bool `state:"nosave"`
@ -175,43 +178,124 @@ func (i *inodeFileState) setSharedHandlesLocked(flags fs.FileFlags, h *handles)
// getHandles returns a set of handles for a new file using i opened with the
// given flags.
func (i *inodeFileState) getHandles(ctx context.Context, flags fs.FileFlags) (*handles, error) {
func (i *inodeFileState) getHandles(ctx context.Context, flags fs.FileFlags, cache *fsutil.CachingInodeOperations) (*handles, error) {
if !i.canShareHandles() {
return newHandles(ctx, i.file, flags)
}
i.handlesMu.Lock()
defer i.handlesMu.Unlock()
h, invalidate, err := i.getHandlesLocked(ctx, flags)
i.handlesMu.Unlock()
if invalidate {
cache.NotifyChangeFD()
if i.hostMappable != nil {
i.hostMappable.NotifyChangeFD()
}
}
return h, err
}
// getHandlesLocked returns a pointer to cached handles and a boolean indicating
// whether previously open read handle was recreated. Host mappings must be
// invalidated if so.
func (i *inodeFileState) getHandlesLocked(ctx context.Context, flags fs.FileFlags) (*handles, bool, error) {
// Do we already have usable shared handles?
if flags.Write {
if i.writeHandles != nil && (i.writeHandlesRW || !flags.Read) {
i.writeHandles.IncRef()
return i.writeHandles, nil
return i.writeHandles, false, nil
}
} else if i.readHandles != nil {
i.readHandles.IncRef()
return i.readHandles, nil
return i.readHandles, false, nil
}
// No; get new handles and cache them for future sharing.
h, err := newHandles(ctx, i.file, flags)
if err != nil {
return nil, err
return nil, false, err
}
// Read handles invalidation is needed if:
// - Mount option 'overlayfs_stale_read' is set
// - Read handle is open: nothing to invalidate otherwise
// - Write handle is not open: file was not open for write and is being open
// for write now (will trigger copy up in overlayfs).
invalidate := false
if i.s.overlayfsStaleRead && i.readHandles != nil && i.writeHandles == nil && flags.Write {
if err := i.recreateReadHandles(ctx, h, flags); err != nil {
return nil, false, err
}
invalidate = true
}
i.setSharedHandlesLocked(flags, h)
return h, nil
return h, invalidate, nil
}
func (i *inodeFileState) recreateReadHandles(ctx context.Context, writer *handles, flags fs.FileFlags) error {
h := writer
if !flags.Read {
// Writer can't be used for read, must create a new handle.
var err error
h, err = newHandles(ctx, i.file, fs.FileFlags{Read: true})
if err != nil {
return err
}
defer h.DecRef()
}
if i.readHandles.Host == nil {
// If current readHandles doesn't have a host FD, it can simply be replaced.
i.readHandles.DecRef()
h.IncRef()
i.readHandles = h
return nil
}
if h.Host == nil {
// Current read handle has a host FD and can't be replaced with one that
// doesn't, because it breaks fsutil.CachedFileObject.FD() contract.
log.Warningf("Read handle can't be invalidated, reads may return stale data")
return nil
}
// Due to a defect in the fsutil.CachedFileObject interface,
// readHandles.Host.FD() may be used outside locks, making it impossible to
// reliably close it. To workaround it, we dup the new FD into the old one, so
// operations on the old will see the new data. Then, make the new handle take
// ownereship of the old FD and mark the old readHandle to not close the FD
// when done.
if err := syscall.Dup2(h.Host.FD(), i.readHandles.Host.FD()); err != nil {
return err
}
h.Host.Close()
h.Host = fd.New(i.readHandles.Host.FD())
i.readHandles.isHostBorrowed = true
i.readHandles.DecRef()
h.IncRef()
i.readHandles = h
return nil
}
// ReadToBlocksAt implements fsutil.CachedFileObject.ReadToBlocksAt.
func (i *inodeFileState) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) {
i.handlesMu.RLock()
defer i.handlesMu.RUnlock()
return i.readHandles.readWriterAt(ctx, int64(offset)).ReadToBlocks(dsts)
n, err := i.readHandles.readWriterAt(ctx, int64(offset)).ReadToBlocks(dsts)
i.handlesMu.RUnlock()
return n, err
}
// WriteFromBlocksAt implements fsutil.CachedFileObject.WriteFromBlocksAt.
func (i *inodeFileState) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
i.handlesMu.RLock()
defer i.handlesMu.RUnlock()
return i.writeHandles.readWriterAt(ctx, int64(offset)).WriteFromBlocks(srcs)
n, err := i.writeHandles.readWriterAt(ctx, int64(offset)).WriteFromBlocks(srcs)
i.handlesMu.RUnlock()
return n, err
}
// SetMaskedAttributes implements fsutil.CachedFileObject.SetMaskedAttributes.
@ -449,7 +533,7 @@ func (i *inodeOperations) NonBlockingOpen(ctx context.Context, p fs.PermMask) (*
}
func (i *inodeOperations) getFileDefault(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
h, err := i.fileState.getHandles(ctx, flags)
h, err := i.fileState.getHandles(ctx, flags, i.cachingInodeOps)
if err != nil {
return nil, err
}

View File

@ -122,6 +122,10 @@ type session struct {
// CachingInodeOperations created by the session.
limitHostFDTranslation bool
// overlayfsStaleRead when set causes the readonly handle to be invalidated
// after file is open for write.
overlayfsStaleRead bool
// connID is a unique identifier for the session connection.
connID string `state:"wait"`
@ -257,6 +261,7 @@ func Root(ctx context.Context, dev string, filesystem fs.Filesystem, superBlockF
aname: o.aname,
superBlockFlags: superBlockFlags,
limitHostFDTranslation: o.limitHostFDTranslation,
overlayfsStaleRead: o.overlayfsStaleRead,
mounter: mounter,
}
s.EnableLeakCheck("gofer.session")

View File

@ -231,6 +231,10 @@ type Config struct {
// ReferenceLeakMode sets reference leak check mode
ReferenceLeakMode refs.LeakMode
// OverlayfsStaleRead causes cached FDs to reopen after a file is opened for
// write to workaround overlayfs limitation on kernels before 4.19.
OverlayfsStaleRead bool
// TestOnlyAllowRunAsCurrentUserWithoutChroot should only be used in
// tests. It allows runsc to start the sandbox process as the current
// user, and without chrooting the sandbox process. This can be
@ -271,6 +275,7 @@ func (c *Config) ToFlags() []string {
"--rootless=" + strconv.FormatBool(c.Rootless),
"--alsologtostderr=" + strconv.FormatBool(c.AlsoLogToStderr),
"--ref-leak-mode=" + refsLeakModeToString(c.ReferenceLeakMode),
"--overlayfs-stale-read=" + strconv.FormatBool(c.OverlayfsStaleRead),
}
// Only include these if set since it is never to be used by users.
if c.TestOnlyAllowRunAsCurrentUserWithoutChroot {

View File

@ -32,6 +32,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/watchdog"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/urpc"
"gvisor.dev/gvisor/runsc/specutils"
)
const (
@ -237,6 +238,9 @@ func (cm *containerManager) Start(args *StartArgs, _ *struct{}) error {
return fmt.Errorf("start arguments must contain stdin, stderr, and stdout followed by at least one file for the container root gofer")
}
// All validation passed, logs the spec for debugging.
specutils.LogSpec(args.Spec)
err := cm.l.startContainer(args.Spec, args.Conf, args.CID, args.FilePayload.Files)
if err != nil {
log.Debugf("containerManager.Start failed %q: %+v: %v", args.CID, args, err)

View File

@ -44,6 +44,7 @@ var allowedSyscalls = seccomp.SyscallRules{
},
syscall.SYS_CLOSE: {},
syscall.SYS_DUP: {},
syscall.SYS_DUP2: {},
syscall.SYS_EPOLL_CREATE1: {},
syscall.SYS_EPOLL_CTL: {},
syscall.SYS_EPOLL_PWAIT: []seccomp.Rule{

View File

@ -703,6 +703,14 @@ func (c *containerMounter) createRootMount(ctx context.Context, conf *Config) (*
log.Infof("Mounting root over 9P, ioFD: %d", fd)
p9FS := mustFindFilesystem("9p")
opts := p9MountOptions(fd, conf.FileAccess)
if conf.OverlayfsStaleRead {
// We can't check for overlayfs here because sandbox is chroot'ed and gofer
// can only send mount options for specs.Mounts (specs.Root is missing
// Options field). So assume root is always on top of overlayfs.
opts = append(opts, "overlayfs_stale_read")
}
rootInode, err := p9FS.Mount(ctx, rootDevice, mf, strings.Join(opts, ","), nil)
if err != nil {
return nil, fmt.Errorf("creating root mount point: %v", err)
@ -737,7 +745,6 @@ func (c *containerMounter) getMountNameAndOptions(conf *Config, m specs.Mount) (
fsName string
opts []string
useOverlay bool
err error
)
switch m.Type {
@ -747,7 +754,12 @@ func (c *containerMounter) getMountNameAndOptions(conf *Config, m specs.Mount) (
fsName = sysfs
case tmpfs:
fsName = m.Type
var err error
opts, err = parseAndFilterOptions(m.Options, tmpfsAllowedOptions...)
if err != nil {
return "", nil, false, err
}
case bind:
fd := c.fds.remove()
@ -763,7 +775,7 @@ func (c *containerMounter) getMountNameAndOptions(conf *Config, m specs.Mount) (
// for now.
log.Warningf("ignoring unknown filesystem type %q", m.Type)
}
return fsName, opts, useOverlay, err
return fsName, opts, useOverlay, nil
}
// mountSubmount mounts volumes inside the container's root. Because mounts may

View File

@ -27,6 +27,7 @@ import (
"flag"
"github.com/google/subcommands"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/p9"
"gvisor.dev/gvisor/pkg/unet"
@ -135,7 +136,7 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
//
// Note that all mount points have been mounted in the proper location in
// setupRootFS().
cleanMounts, err := resolveMounts(spec.Mounts, root)
cleanMounts, err := resolveMounts(conf, spec.Mounts, root)
if err != nil {
Fatalf("Failure to resolve mounts: %v", err)
}
@ -380,7 +381,7 @@ func setupMounts(mounts []specs.Mount, root string) error {
// Otherwise, it may follow symlinks to locations that would be overwritten
// with another mount point and return the wrong location. In short, make sure
// setupMounts() has been called before.
func resolveMounts(mounts []specs.Mount, root string) ([]specs.Mount, error) {
func resolveMounts(conf *boot.Config, mounts []specs.Mount, root string) ([]specs.Mount, error) {
cleanMounts := make([]specs.Mount, 0, len(mounts))
for _, m := range mounts {
if m.Type != "bind" || !specutils.IsSupportedDevMount(m) {
@ -395,8 +396,15 @@ func resolveMounts(mounts []specs.Mount, root string) ([]specs.Mount, error) {
if err != nil {
panic(fmt.Sprintf("%q could not be made relative to %q: %v", dst, root, err))
}
opts, err := adjustMountOptions(conf, filepath.Join(root, relDst), m.Options)
if err != nil {
return nil, err
}
cpy := m
cpy.Destination = filepath.Join("/", relDst)
cpy.Options = opts
cleanMounts = append(cleanMounts, cpy)
}
return cleanMounts, nil
@ -453,3 +461,20 @@ func resolveSymlinksImpl(root, base, rel string, followCount uint) (string, erro
}
return base, nil
}
// adjustMountOptions adds 'overlayfs_stale_read' if mounting over overlayfs.
func adjustMountOptions(conf *boot.Config, path string, opts []string) ([]string, error) {
rv := make([]string, len(opts))
copy(rv, opts)
if conf.OverlayfsStaleRead {
statfs := syscall.Statfs_t{}
if err := syscall.Statfs(path, &statfs); err != nil {
return nil, err
}
if statfs.Type == unix.OVERLAYFS_SUPER_MAGIC {
rv = append(rv, "overlayfs_stale_read")
}
}
return rv, nil
}

View File

@ -2074,6 +2074,43 @@ func TestNetRaw(t *testing.T) {
}
}
// TestOverlayfsStaleRead most basic test that '--overlayfs-stale-read' works.
func TestOverlayfsStaleRead(t *testing.T) {
conf := testutil.TestConfig()
conf.OverlayfsStaleRead = true
in, err := ioutil.TempFile(testutil.TmpDir(), "stale-read.in")
if err != nil {
t.Fatalf("ioutil.TempFile() failed: %v", err)
}
defer in.Close()
if _, err := in.WriteString("stale data"); err != nil {
t.Fatalf("in.Write() failed: %v", err)
}
out, err := ioutil.TempFile(testutil.TmpDir(), "stale-read.out")
if err != nil {
t.Fatalf("ioutil.TempFile() failed: %v", err)
}
defer out.Close()
const want = "foobar"
cmd := fmt.Sprintf("cat %q && echo %q> %q && cp %q %q", in.Name(), want, in.Name(), in.Name(), out.Name())
spec := testutil.NewSpecWithArgs("/bin/bash", "-c", cmd)
if err := run(spec, conf); err != nil {
t.Fatalf("Error running container: %v", err)
}
gotBytes, err := ioutil.ReadAll(out)
if err != nil {
t.Fatalf("out.Read() failed: %v", err)
}
got := strings.TrimSpace(string(gotBytes))
if want != got {
t.Errorf("Wrong content in out file, got: %q. want: %q", got, want)
}
}
// executeSync synchronously executes a new process.
func (cont *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) {
pid, err := cont.Execute(args)

View File

@ -41,35 +41,36 @@ import (
var (
// Although these flags are not part of the OCI spec, they are used by
// Docker, and thus should not be changed.
rootDir = flag.String("root", "", "root directory for storage of container state")
logFilename = flag.String("log", "", "file path where internal debug information is written, default is stdout")
logFormat = flag.String("log-format", "text", "log format: text (default), json, or json-k8s")
debug = flag.Bool("debug", false, "enable debug logging")
showVersion = flag.Bool("version", false, "show version and exit")
rootDir = flag.String("root", "", "root directory for storage of container state.")
logFilename = flag.String("log", "", "file path where internal debug information is written, default is stdout.")
logFormat = flag.String("log-format", "text", "log format: text (default), json, or json-k8s.")
debug = flag.Bool("debug", false, "enable debug logging.")
showVersion = flag.Bool("version", false, "show version and exit.")
// These flags are unique to runsc, and are used to configure parts of the
// system that are not covered by the runtime spec.
// Debugging flags.
debugLog = flag.String("debug-log", "", "additional location for logs. If it ends with '/', log files are created inside the directory with default names. The following variables are available: %TIMESTAMP%, %COMMAND%.")
logPackets = flag.Bool("log-packets", false, "enable network packet logging")
logPackets = flag.Bool("log-packets", false, "enable network packet logging.")
logFD = flag.Int("log-fd", -1, "file descriptor to log to. If set, the 'log' flag is ignored.")
debugLogFD = flag.Int("debug-log-fd", -1, "file descriptor to write debug logs to. If set, the 'debug-log-dir' flag is ignored.")
debugLogFormat = flag.String("debug-log-format", "text", "log format: text (default), json, or json-k8s")
alsoLogToStderr = flag.Bool("alsologtostderr", false, "send log messages to stderr")
debugLogFormat = flag.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.")
alsoLogToStderr = flag.Bool("alsologtostderr", false, "send log messages to stderr.")
// Debugging flags: strace related
strace = flag.Bool("strace", false, "enable strace")
strace = flag.Bool("strace", false, "enable strace.")
straceSyscalls = flag.String("strace-syscalls", "", "comma-separated list of syscalls to trace. If --strace is true and this list is empty, then all syscalls will be traced.")
straceLogSize = flag.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs")
straceLogSize = flag.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs.")
// Flags that control sandbox runtime behavior.
platformName = flag.String("platform", "ptrace", "specifies which platform to use: ptrace (default), kvm")
platformName = flag.String("platform", "ptrace", "specifies which platform to use: ptrace (default), kvm.")
network = flag.String("network", "sandbox", "specifies which network to use: sandbox (default), host, none. Using network inside the sandbox is more secure because it's isolated from the host network.")
gso = flag.Bool("gso", true, "enable generic segmenation offload")
gso = flag.Bool("gso", true, "enable generic segmenation offload.")
fileAccess = flag.String("file-access", "exclusive", "specifies which filesystem to use for the root mount: exclusive (default), shared. Volume mounts are always shared.")
fsGoferHostUDS = flag.Bool("fsgofer-host-uds", false, "Allow the gofer to mount Unix Domain Sockets.")
fsGoferHostUDS = flag.Bool("fsgofer-host-uds", false, "allow the gofer to mount Unix Domain Sockets.")
overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable overlay. All modifications are stored in memory inside the sandbox.")
overlayfsStaleRead = flag.Bool("overlayfs-stale-read", false, "reopen cached FDs after a file is opened for write to workaround overlayfs limitation on kernels before 4.19.")
watchdogAction = flag.String("watchdog-action", "log", "sets what action the watchdog takes when triggered: log (default), panic.")
panicSignal = flag.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.")
profile = flag.Bool("profile", false, "prepares the sandbox to use Golang profiler. Note that enabling profiler loosens the seccomp protection added to the sandbox (DO NOT USE IN PRODUCTION).")
@ -212,6 +213,7 @@ func main() {
Rootless: *rootless,
AlsoLogToStderr: *alsoLogToStderr,
ReferenceLeakMode: refsLeakMode,
OverlayfsStaleRead: *overlayfsStaleRead,
TestOnlyAllowRunAsCurrentUserWithoutChroot: *testOnlyAllowRunAsCurrentUserWithoutChroot,
TestOnlyTestNameEnv: *testOnlyTestNameEnv,