Skip to content

Commit

Permalink
dmz: add fallbacks to handle noexec for O_TMPFILE and mktemp()
Browse files Browse the repository at this point in the history
Previously, if /var/run was mounted noexec, our cloned binary logic
would not work if memfd_create(2) was not available because we would try
to exec a binary that is on a noexec filesystem.

We cannot guarantee there will be an executable filesystem on the system
(other than mounting one ourselves, which would cause a bunch of other
headaches) but we can at least try the obvious options (/tmp, /bin, and
/). If none of these work, we will have to fail.

Reported-by: lifubang <[email protected]>
Signed-off-by: Aleksa Sarai <[email protected]>
  • Loading branch information
cyphar committed Sep 22, 2023
1 parent 0e9a335 commit e089db3
Showing 1 changed file with 53 additions and 4 deletions.
57 changes: 53 additions & 4 deletions libcontainer/dmz/cloned_binary_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"strconv"

"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
Expand All @@ -19,6 +20,23 @@ var (
_ SealFunc = sealFile
)

func isExecutable(f *os.File) bool {
if err := unix.Faccessat(int(f.Fd()), "", unix.X_OK, unix.AT_EACCESS|unix.AT_EMPTY_PATH); err == nil {
return true
} else if err == unix.EACCES {
return false
}
path := "/proc/self/fd/" + strconv.Itoa(int(f.Fd()))
if err := unix.Access(path, unix.X_OK); err == nil {
return true
} else if err == unix.EACCES {
return false
}
// Cannot check -- assume it's executable (if not, exec will fail).
logrus.Debugf("cannot do X_OK check on binary %s -- assuming it's executable", f.Name())
return true
}

const baseMemfdSeals = unix.F_SEAL_SEAL | unix.F_SEAL_SHRINK | unix.F_SEAL_GROW | unix.F_SEAL_WRITE

func sealMemfd(f **os.File) error {
Expand Down Expand Up @@ -106,15 +124,46 @@ func getSealableFile(comment, tmpDir string) (file *os.File, sealFn SealFunc, er
return
}
logrus.Debugf("memfd cloned binary failed, falling back to O_TMPFILE: %v", err)

// The tmpDir here (c.root) might be mounted noexec, so we need a couple of
// fallbacks to try. It's possible that none of these are writable and
// executable, in which case there's nothing we can practically do (other
// than mounting our own executable tmpfs, which would have its own
// issues).
tmpDirs := []string{
tmpDir,
os.TempDir(),
"/tmp",
".",
"/bin",
"/",
}

// Try to fallback to O_TMPFILE (supported since Linux 3.11).
file, sealFn, err = otmpfile(tmpDir)
if err == nil {
for _, dir := range tmpDirs {
file, sealFn, err = otmpfile(dir)
if err != nil {
continue
}
if !isExecutable(file) {
logrus.Debugf("tmpdir %s is noexec -- trying a different tmpdir", dir)
file.Close()
continue
}
return
}
logrus.Debugf("O_TMPFILE cloned binary failed, falling back to mktemp(): %v", err)
// Finally, try a classic unlinked temporary file.
file, sealFn, err = mktemp(tmpDir)
if err == nil {
for _, dir := range tmpDirs {
file, sealFn, err = mktemp(dir)
if err != nil {
continue
}
if !isExecutable(file) {
logrus.Debugf("tmpdir %s is noexec -- trying a different tmpdir", dir)
file.Close()
continue
}
return
}
return nil, nil, fmt.Errorf("could not create sealable file for cloned binary: %w", err)
Expand Down

0 comments on commit e089db3

Please sign in to comment.