Skip to content

Commit aa8abdf

Browse files
ayushr2gvisor-bot
authored andcommitted
Check that the sandbox process does not have open directory FDs.
This is important because with directfs, the sandbox process can now make openat(2) calls. We want to ensure we didn't leak any host filesystem file descriptor which could be used by a compromised sentry to potentially escape. PiperOrigin-RevId: 537123941
1 parent 16f76ac commit aa8abdf

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

runsc/cmd/boot.go

+51
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
"io/ioutil"
2121
"os"
2222
"os/exec"
23+
"path/filepath"
2324
"runtime"
2425
"runtime/debug"
26+
"strconv"
2527
"strings"
2628

2729
"github.com/google/subcommands"
@@ -419,6 +421,8 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
419421

420422
ifb.procMountSyncFD!=-1 {
421423
l.PreSeccompCallback=func() {
424+
// Call validateOpenFDs() before umounting /proc.
425+
validateOpenFDs(bootArgs.PassFDs)
422426
// Umount /proc right before installing seccomp filters.
423427
umountProc(b.procMountSyncFD)
424428
}
@@ -525,3 +529,50 @@ func umountProc(syncFD int) {
525529
util.Fatalf("/proc is still accessible")
526530
}
527531
}
532+
533+
// validateOpenFDs checks that the sandbox process does not have any open
534+
// directory FDs.
535+
funcvalidateOpenFDs(passFDs []boot.FDMapping) {
536+
passHostFDs:=make(map[int]struct{})
537+
for_, passFD:=rangepassFDs {
538+
passHostFDs[passFD.Host] =struct{}{}
539+
}
540+
constselfFDDir="/proc/self/fd"
541+
iferr:=filepath.WalkDir(selfFDDir, func(pathstring, d os.DirEntry, errerror) error {
542+
iferr!=nil {
543+
returnerr
544+
}
545+
ifd.Type() !=os.ModeSymlink {
546+
// All entries are symlinks. Ignore the callback for fd directory itself.
547+
returnnil
548+
}
549+
iffdInfo, err:=os.Stat(path); err!=nil {
550+
ifos.IsNotExist(err) {
551+
// Ignore FDs that are now closed. For example, the FD to selfFDDir that
552+
// was opened by filepath.WalkDir() to read dirents.
553+
returnnil
554+
}
555+
returnfmt.Errorf("os.Stat(%s) failed: %v", path, err)
556+
} elseif!fdInfo.IsDir() {
557+
returnnil
558+
}
559+
// Uh-oh. This is a directory FD.
560+
fdNo, err:=strconv.Atoi(d.Name())
561+
iferr!=nil {
562+
returnfmt.Errorf("strconv.Atoi(%s) failed: %v", d.Name(), err)
563+
}
564+
dirLink, err:=os.Readlink(path)
565+
iferr!=nil {
566+
returnfmt.Errorf("os.Readlink(%s) failed: %v", path, err)
567+
}
568+
if_, ok:=passHostFDs[fdNo]; ok {
569+
// Passed FDs are allowed to be directories. The user must be knowing
570+
// what they are doing. Log a warning regardless.
571+
log.Warningf("Sandbox has access to FD %d, which is a directory for %s", fdNo, dirLink)
572+
returnnil
573+
}
574+
returnfmt.Errorf("FD %d is a directory for %s", fdNo, dirLink)
575+
}); err!=nil {
576+
util.Fatalf("WalkDir(%s) failed: %v", selfFDDir, err)
577+
}
578+
}

0 commit comments

Comments
 (0)
close