Skip to content

Commit e089db3

Browse files
committed
dmz: add fallbacks to handle noexec for O_TMPFILE and mktemp()
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]>
1 parent 0e9a335 commit e089db3

File tree

1 file changed

+53
-4
lines changed

1 file changed

+53
-4
lines changed

libcontainer/dmz/cloned_binary_linux.go

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"os"
8+
"strconv"
89

910
"github.com/sirupsen/logrus"
1011
"golang.org/x/sys/unix"
@@ -19,6 +20,23 @@ var (
1920
_ SealFunc = sealFile
2021
)
2122

23+
func isExecutable(f *os.File) bool {
24+
if err := unix.Faccessat(int(f.Fd()), "", unix.X_OK, unix.AT_EACCESS|unix.AT_EMPTY_PATH); err == nil {
25+
return true
26+
} else if err == unix.EACCES {
27+
return false
28+
}
29+
path := "/proc/self/fd/" + strconv.Itoa(int(f.Fd()))
30+
if err := unix.Access(path, unix.X_OK); err == nil {
31+
return true
32+
} else if err == unix.EACCES {
33+
return false
34+
}
35+
// Cannot check -- assume it's executable (if not, exec will fail).
36+
logrus.Debugf("cannot do X_OK check on binary %s -- assuming it's executable", f.Name())
37+
return true
38+
}
39+
2240
const baseMemfdSeals = unix.F_SEAL_SEAL | unix.F_SEAL_SHRINK | unix.F_SEAL_GROW | unix.F_SEAL_WRITE
2341

2442
func sealMemfd(f **os.File) error {
@@ -106,15 +124,46 @@ func getSealableFile(comment, tmpDir string) (file *os.File, sealFn SealFunc, er
106124
return
107125
}
108126
logrus.Debugf("memfd cloned binary failed, falling back to O_TMPFILE: %v", err)
127+
128+
// The tmpDir here (c.root) might be mounted noexec, so we need a couple of
129+
// fallbacks to try. It's possible that none of these are writable and
130+
// executable, in which case there's nothing we can practically do (other
131+
// than mounting our own executable tmpfs, which would have its own
132+
// issues).
133+
tmpDirs := []string{
134+
tmpDir,
135+
os.TempDir(),
136+
"/tmp",
137+
".",
138+
"/bin",
139+
"/",
140+
}
141+
109142
// Try to fallback to O_TMPFILE (supported since Linux 3.11).
110-
file, sealFn, err = otmpfile(tmpDir)
111-
if err == nil {
143+
for _, dir := range tmpDirs {
144+
file, sealFn, err = otmpfile(dir)
145+
if err != nil {
146+
continue
147+
}
148+
if !isExecutable(file) {
149+
logrus.Debugf("tmpdir %s is noexec -- trying a different tmpdir", dir)
150+
file.Close()
151+
continue
152+
}
112153
return
113154
}
114155
logrus.Debugf("O_TMPFILE cloned binary failed, falling back to mktemp(): %v", err)
115156
// Finally, try a classic unlinked temporary file.
116-
file, sealFn, err = mktemp(tmpDir)
117-
if err == nil {
157+
for _, dir := range tmpDirs {
158+
file, sealFn, err = mktemp(dir)
159+
if err != nil {
160+
continue
161+
}
162+
if !isExecutable(file) {
163+
logrus.Debugf("tmpdir %s is noexec -- trying a different tmpdir", dir)
164+
file.Close()
165+
continue
166+
}
118167
return
119168
}
120169
return nil, nil, fmt.Errorf("could not create sealable file for cloned binary: %w", err)

0 commit comments

Comments
 (0)